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