Home | History | Annotate | Download | only in wear
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.packageinstaller.wear;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationChannel;
     21 import android.app.NotificationManager;
     22 import android.app.Service;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.FeatureInfo;
     26 import android.content.pm.IPackageDeleteObserver;
     27 import android.content.pm.PackageInfo;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.PackageParser;
     30 import android.database.Cursor;
     31 import android.net.Uri;
     32 import android.os.Build;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.HandlerThread;
     36 import android.os.IBinder;
     37 import android.os.Looper;
     38 import android.os.Message;
     39 import android.os.ParcelFileDescriptor;
     40 import android.os.PowerManager;
     41 import android.os.Process;
     42 import android.util.ArrayMap;
     43 import android.util.Log;
     44 import android.util.Pair;
     45 
     46 import com.android.packageinstaller.DeviceUtils;
     47 import com.android.packageinstaller.PackageUtil;
     48 import com.android.packageinstaller.R;
     49 
     50 import java.io.File;
     51 import java.io.FileNotFoundException;
     52 import java.util.HashSet;
     53 import java.util.List;
     54 import java.util.Map;
     55 import java.util.Set;
     56 
     57 /**
     58  * Service that will install/uninstall packages. It will check for permissions and features as well.
     59  *
     60  * -----------
     61  *
     62  * Debugging information:
     63  *
     64  *  Install Action example:
     65  *  adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
     66  *     -d package://com.google.android.gms \
     67  *     --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
     68  *     --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
     69  *     --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
     70  *     --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
     71  *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
     72  *
     73  *  Uninstall Action example:
     74  *  adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
     75  *     -d package://com.google.android.gms \
     76  *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
     77  *
     78  *  Retry GMS:
     79  *  adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
     80  *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
     81  */
     82 public class WearPackageInstallerService extends Service {
     83     private static final String TAG = "WearPkgInstallerService";
     84 
     85     private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
     86 
     87     private final int START_INSTALL = 1;
     88     private final int START_UNINSTALL = 2;
     89 
     90     private int mInstallNotificationId = 1;
     91     private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
     92 
     93     private final class ServiceHandler extends Handler {
     94         public ServiceHandler(Looper looper) {
     95             super(looper);
     96         }
     97 
     98         public void handleMessage(Message msg) {
     99             switch (msg.what) {
    100                 case START_INSTALL:
    101                     installPackage(msg.getData());
    102                     break;
    103                 case START_UNINSTALL:
    104                     uninstallPackage(msg.getData());
    105                     break;
    106             }
    107         }
    108     }
    109     private ServiceHandler mServiceHandler;
    110     private NotificationChannel mNotificationChannel;
    111     private static volatile PowerManager.WakeLock lockStatic = null;
    112 
    113     @Override
    114     public IBinder onBind(Intent intent) {
    115         return null;
    116     }
    117 
    118     @Override
    119     public void onCreate() {
    120         super.onCreate();
    121         HandlerThread thread = new HandlerThread("PackageInstallerThread",
    122                 Process.THREAD_PRIORITY_BACKGROUND);
    123         thread.start();
    124 
    125         mServiceHandler = new ServiceHandler(thread.getLooper());
    126     }
    127 
    128     @Override
    129     public int onStartCommand(Intent intent, int flags, int startId) {
    130         if (!DeviceUtils.isWear(this)) {
    131             Log.w(TAG, "Not running on wearable.");
    132             finishServiceEarly(startId);
    133             return START_NOT_STICKY;
    134         }
    135 
    136         if (intent == null) {
    137             Log.w(TAG, "Got null intent.");
    138             finishServiceEarly(startId);
    139             return START_NOT_STICKY;
    140         }
    141 
    142         if (Log.isLoggable(TAG, Log.DEBUG)) {
    143             Log.d(TAG, "Got install/uninstall request " + intent);
    144         }
    145 
    146         Uri packageUri = intent.getData();
    147         if (packageUri == null) {
    148             Log.e(TAG, "No package URI in intent");
    149             finishServiceEarly(startId);
    150             return START_NOT_STICKY;
    151         }
    152 
    153         final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
    154         if (packageName == null) {
    155             Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
    156             finishServiceEarly(startId);
    157             return START_NOT_STICKY;
    158         }
    159 
    160         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
    161         if (!lock.isHeld()) {
    162             lock.acquire();
    163         }
    164 
    165         Bundle intentBundle = intent.getExtras();
    166         if (intentBundle == null) {
    167             intentBundle = new Bundle();
    168         }
    169         WearPackageArgs.setStartId(intentBundle, startId);
    170         WearPackageArgs.setPackageName(intentBundle, packageName);
    171         Message msg;
    172         String notifTitle;
    173         if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
    174             msg = mServiceHandler.obtainMessage(START_INSTALL);
    175             notifTitle = getString(R.string.installing);
    176         } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
    177             msg = mServiceHandler.obtainMessage(START_UNINSTALL);
    178             notifTitle = getString(R.string.uninstalling);
    179         } else {
    180             Log.e(TAG, "Unknown action : " + intent.getAction());
    181             finishServiceEarly(startId);
    182             return START_NOT_STICKY;
    183         }
    184         Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
    185         startForeground(notifPair.first, notifPair.second);
    186         msg.setData(intentBundle);
    187         mServiceHandler.sendMessage(msg);
    188         return START_NOT_STICKY;
    189     }
    190 
    191     private void installPackage(Bundle argsBundle) {
    192         int startId = WearPackageArgs.getStartId(argsBundle);
    193         final String packageName = WearPackageArgs.getPackageName(argsBundle);
    194         final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
    195         final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
    196         boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
    197         boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
    198         int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
    199         int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
    200         String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
    201         boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
    202 
    203         if (Log.isLoggable(TAG, Log.DEBUG)) {
    204             Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
    205                     ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
    206                     checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
    207                     ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
    208                     companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
    209                     ", skipIfLowerVersion: " + skipIfLowerVersion);
    210         }
    211         final PackageManager pm = getPackageManager();
    212         File tempFile = null;
    213         int installFlags = 0;
    214         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
    215         boolean messageSent = false;
    216         try {
    217             PackageInfo existingPkgInfo = null;
    218             try {
    219                 existingPkgInfo = pm.getPackageInfo(packageName,
    220                         PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
    221                 if (existingPkgInfo != null) {
    222                     installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
    223                 }
    224             } catch (PackageManager.NameNotFoundException e) {
    225                 // Ignore this exception. We could not find the package, will treat as a new
    226                 // installation.
    227             }
    228             if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
    229                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    230                     Log.d(TAG, "Replacing package:" + packageName);
    231                 }
    232             }
    233             // TODO(28021618): This was left as a temp file due to the fact that this code is being
    234             //       deprecated and that we need the bare minimum to continue working moving forward
    235             //       If this code is used as reference, this permission logic might want to be
    236             //       reworked to use a stream instead of a file so that we don't need to write a
    237             //       file at all.  Note that there might be some trickiness with opening a stream
    238             //       for multiple users.
    239             ParcelFileDescriptor parcelFd = getContentResolver()
    240                     .openFileDescriptor(assetUri, "r");
    241             tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
    242                     parcelFd, packageName, compressionAlg);
    243             if (tempFile == null) {
    244                 Log.e(TAG, "Could not create a temp file from FD for " + packageName);
    245                 return;
    246             }
    247             PackageParser.Package pkg = PackageUtil.getPackageInfo(this, tempFile);
    248             if (pkg == null) {
    249                 Log.e(TAG, "Could not parse apk information for " + packageName);
    250                 return;
    251             }
    252 
    253             if (!pkg.packageName.equals(packageName)) {
    254                 Log.e(TAG, "Wearable Package Name has to match what is provided for " +
    255                         packageName);
    256                 return;
    257             }
    258 
    259             pkg.applicationInfo.sourceDir = tempFile.getPath();
    260             pkg.applicationInfo.publicSourceDir = tempFile.getPath();
    261             getLabelAndUpdateNotification(packageName,
    262                     getString(R.string.installing_app, pkg.applicationInfo.loadLabel(pm)));
    263 
    264             List<String> wearablePerms = pkg.requestedPermissions;
    265 
    266             // Log if the installed pkg has a higher version number.
    267             if (existingPkgInfo != null) {
    268                 if (existingPkgInfo.getLongVersionCode() == pkg.getLongVersionCode()) {
    269                     if (skipIfSameVersion) {
    270                         Log.w(TAG, "Version number (" + pkg.getLongVersionCode() +
    271                                 ") of new app is equal to existing app for " + packageName +
    272                                 "; not installing due to versionCheck");
    273                         return;
    274                     } else {
    275                         Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() +
    276                                 ") is equal to existing app for " + packageName);
    277                     }
    278                 } else if (existingPkgInfo.getLongVersionCode() > pkg.getLongVersionCode()) {
    279                     if (skipIfLowerVersion) {
    280                         // Starting in Feldspar, we are not going to allow downgrades of any app.
    281                         Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() +
    282                                 ") is lower than existing app ( "
    283                                 + existingPkgInfo.getLongVersionCode() +
    284                                 ") for " + packageName + "; not installing due to versionCheck");
    285                         return;
    286                     } else {
    287                         Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() +
    288                                 ") is lower than existing app ( "
    289                                 + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
    290                     }
    291                 }
    292 
    293                 // Following the Android Phone model, we should only check for permissions for any
    294                 // newly defined perms.
    295                 if (existingPkgInfo.requestedPermissions != null) {
    296                     for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
    297                         // If the permission is granted, then we will not ask to request it again.
    298                         if ((existingPkgInfo.requestedPermissionsFlags[i] &
    299                                 PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
    300                             if (Log.isLoggable(TAG, Log.DEBUG)) {
    301                                 Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
    302                                         " is already granted for " + packageName);
    303                             }
    304                             wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
    305                         }
    306                     }
    307                 }
    308             }
    309 
    310             // Check that the wearable has all the features.
    311             boolean hasAllFeatures = true;
    312             if (pkg.reqFeatures != null) {
    313                 for (FeatureInfo feature : pkg.reqFeatures) {
    314                     if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
    315                             (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
    316                         Log.e(TAG, "Wearable does not have required feature: " + feature +
    317                                 " for " + packageName);
    318                         hasAllFeatures = false;
    319                     }
    320                 }
    321             }
    322 
    323             if (!hasAllFeatures) {
    324                 return;
    325             }
    326 
    327             // Check permissions on both the new wearable package and also on the already installed
    328             // wearable package.
    329             // If the app is targeting API level 23, we will also start a service in ClockworkHome
    330             // which will ultimately prompt the user to accept/reject permissions.
    331             if (checkPerms && !checkPermissions(pkg, companionSdkVersion, companionDeviceVersion,
    332                     permUri, wearablePerms, tempFile)) {
    333                 Log.w(TAG, "Wearable does not have enough permissions.");
    334                 return;
    335             }
    336 
    337             // Finally install the package.
    338             ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
    339             PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
    340                     new PackageInstallListener(this, lock, startId, packageName));
    341 
    342             messageSent = true;
    343             Log.i(TAG, "Sent installation request for " + packageName);
    344         } catch (FileNotFoundException e) {
    345             Log.e(TAG, "Could not find the file with URI " + assetUri, e);
    346         } finally {
    347             if (!messageSent) {
    348                 // Some error happened. If the message has been sent, we can wait for the observer
    349                 // which will finish the service.
    350                 if (tempFile != null) {
    351                     tempFile.delete();
    352                 }
    353                 finishService(lock, startId);
    354             }
    355         }
    356     }
    357 
    358     // TODO: This was left using the old PackageManager API due to the fact that this code is being
    359     //       deprecated and that we need the bare minimum to continue working moving forward
    360     //       If this code is used as reference, this logic should be reworked to use the new
    361     //       PackageInstaller APIs similar to how installPackage was reworked
    362     private void uninstallPackage(Bundle argsBundle) {
    363         int startId = WearPackageArgs.getStartId(argsBundle);
    364         final String packageName = WearPackageArgs.getPackageName(argsBundle);
    365 
    366         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
    367         final PackageManager pm = getPackageManager();
    368         try {
    369             PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
    370             getLabelAndUpdateNotification(packageName,
    371                     getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
    372 
    373             // Found package, send uninstall request.
    374             pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
    375                     PackageManager.DELETE_ALL_USERS);
    376 
    377             Log.i(TAG, "Sent delete request for " + packageName);
    378         } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
    379             // Couldn't find the package, no need to call uninstall.
    380             Log.w(TAG, "Could not find package, not deleting " + packageName, e);
    381             finishService(lock, startId);
    382         }
    383     }
    384 
    385     private boolean checkPermissions(PackageParser.Package pkg, int companionSdkVersion,
    386             int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
    387             File apkFile) {
    388         // Assumption: We are running on Android O.
    389         // If the Phone App is targeting M, all permissions may not have been granted to the phone
    390         // app. If the Wear App is then not targeting M, there may be permissions that are not
    391         // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
    392         // app.
    393         if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
    394             // Install the app if Wear App is ready for the new perms model.
    395             return true;
    396         }
    397 
    398         if (!doesWearHaveUngrantedPerms(pkg.packageName, permUri, wearablePermissions)) {
    399             // All permissions requested by the watch are already granted on the phone, no need
    400             // to do anything.
    401             return true;
    402         }
    403 
    404         // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
    405         if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
    406             Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
    407                     + "phone app is targeting at least 23, will continue.");
    408         }
    409 
    410         return false;
    411     }
    412 
    413     /**
    414      * Given a {@string packageName} corresponding to a phone app, query the provider for all the
    415      * perms that are granted.
    416      *
    417      * @return true if the Wear App has any perms that have not been granted yet on the phone side.
    418      * @return true if there is any error cases.
    419      */
    420     private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
    421             List<String> wearablePermissions) {
    422         if (permUri == null) {
    423             Log.e(TAG, "Permission URI is null");
    424             // Pretend there is an ungranted permission to avoid installing for error cases.
    425             return true;
    426         }
    427         Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
    428         if (permCursor == null) {
    429             Log.e(TAG, "Could not get the cursor for the permissions");
    430             // Pretend there is an ungranted permission to avoid installing for error cases.
    431             return true;
    432         }
    433 
    434         Set<String> grantedPerms = new HashSet<>();
    435         Set<String> ungrantedPerms = new HashSet<>();
    436         while(permCursor.moveToNext()) {
    437             // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
    438             // verify their types.
    439             if (permCursor.getColumnCount() == 2
    440                     && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
    441                     && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
    442                 String perm = permCursor.getString(0);
    443                 Integer granted = permCursor.getInt(1);
    444                 if (granted == 1) {
    445                     grantedPerms.add(perm);
    446                 } else {
    447                     ungrantedPerms.add(perm);
    448                 }
    449             }
    450         }
    451         permCursor.close();
    452 
    453         boolean hasUngrantedPerm = false;
    454         for (String wearablePerm : wearablePermissions) {
    455             if (!grantedPerms.contains(wearablePerm)) {
    456                 hasUngrantedPerm = true;
    457                 if (!ungrantedPerms.contains(wearablePerm)) {
    458                     // This is an error condition. This means that the wearable has permissions that
    459                     // are not even declared in its host app. This is a developer error.
    460                     Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
    461                             + "\" that is not defined in the host application's manifest.");
    462                 } else {
    463                     Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
    464                             "\" that is not granted in the host application.");
    465                 }
    466             }
    467         }
    468         return hasUngrantedPerm;
    469     }
    470 
    471     /** Finishes the service after fulfilling obligation to call startForeground. */
    472     private void finishServiceEarly(int startId) {
    473         Pair<Integer, Notification> notifPair = buildNotification(
    474                 getApplicationContext().getPackageName(), "");
    475         startForeground(notifPair.first, notifPair.second);
    476         finishService(null, startId);
    477     }
    478 
    479     private void finishService(PowerManager.WakeLock lock, int startId) {
    480         if (lock != null && lock.isHeld()) {
    481             lock.release();
    482         }
    483         stopSelf(startId);
    484     }
    485 
    486     private synchronized PowerManager.WakeLock getLock(Context context) {
    487         if (lockStatic == null) {
    488             PowerManager mgr =
    489                     (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    490             lockStatic = mgr.newWakeLock(
    491                     PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
    492             lockStatic.setReferenceCounted(true);
    493         }
    494         return lockStatic;
    495     }
    496 
    497     private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
    498         private Context mContext;
    499         private PowerManager.WakeLock mWakeLock;
    500         private int mStartId;
    501         private String mApplicationPackageName;
    502         private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
    503                 int startId, String applicationPackageName) {
    504             mContext = context;
    505             mWakeLock = wakeLock;
    506             mStartId = startId;
    507             mApplicationPackageName = applicationPackageName;
    508         }
    509 
    510         @Override
    511         public void installBeginning() {
    512             Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
    513         }
    514 
    515         @Override
    516         public void installSucceeded() {
    517             try {
    518                 Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
    519 
    520                 // Delete tempFile from the file system.
    521                 File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
    522                 if (tempFile != null) {
    523                     tempFile.delete();
    524                 }
    525             } finally {
    526                 finishService(mWakeLock, mStartId);
    527             }
    528         }
    529 
    530         @Override
    531         public void installFailed(int errorCode, String errorDesc) {
    532             Log.e(TAG, "Package install failed " + mApplicationPackageName
    533                     + ", errorCode " + errorCode);
    534             finishService(mWakeLock, mStartId);
    535         }
    536     }
    537 
    538     private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
    539         private PowerManager.WakeLock mWakeLock;
    540         private int mStartId;
    541 
    542         private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
    543             mWakeLock = wakeLock;
    544             mStartId = startId;
    545         }
    546 
    547         public void packageDeleted(String packageName, int returnCode) {
    548             try {
    549                 if (returnCode >= 0) {
    550                     Log.i(TAG, "Package " + packageName + " was uninstalled.");
    551                 } else {
    552                     Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " +
    553                             returnCode);
    554                 }
    555             } finally {
    556                 finishService(mWakeLock, mStartId);
    557             }
    558         }
    559     }
    560 
    561     private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
    562             final String title) {
    563         int notifId;
    564         if (mNotifIdMap.containsKey(packageName)) {
    565             notifId = mNotifIdMap.get(packageName);
    566         } else {
    567             notifId = mInstallNotificationId++;
    568             mNotifIdMap.put(packageName, notifId);
    569         }
    570 
    571         if (mNotificationChannel == null) {
    572             mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
    573                     getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
    574             NotificationManager notificationManager = getSystemService(NotificationManager.class);
    575             notificationManager.createNotificationChannel(mNotificationChannel);
    576         }
    577         return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
    578             .setSmallIcon(R.drawable.ic_file_download)
    579             .setContentTitle(title)
    580             .build());
    581     }
    582 
    583     private void getLabelAndUpdateNotification(String packageName, String title) {
    584         // Update notification since we have a label now.
    585         NotificationManager notificationManager = getSystemService(NotificationManager.class);
    586         Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
    587         notificationManager.notify(notifPair.first, notifPair.second);
    588     }
    589 }
    590