• 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.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 {
ServiceHandler(Looper looper)94         public ServiceHandler(Looper looper) {
95             super(looper);
96         }
97 
handleMessage(Message msg)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
onBind(Intent intent)114     public IBinder onBind(Intent intent) {
115         return null;
116     }
117 
118     @Override
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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 
installPackage(Bundle argsBundle)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
uninstallPackage(Bundle argsBundle)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 
checkPermissions(PackageParser.Package pkg, int companionSdkVersion, int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, File apkFile)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      */
doesWearHaveUngrantedPerms(String packageName, Uri permUri, List<String> wearablePermissions)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. */
finishServiceEarly(int startId)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 
finishService(PowerManager.WakeLock lock, int startId)479     private void finishService(PowerManager.WakeLock lock, int startId) {
480         if (lock != null && lock.isHeld()) {
481             lock.release();
482         }
483         stopSelf(startId);
484     }
485 
getLock(Context context)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;
PackageInstallListener(Context context, PowerManager.WakeLock wakeLock, int startId, String applicationPackageName)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
installBeginning()511         public void installBeginning() {
512             Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
513         }
514 
515         @Override
installSucceeded()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
installFailed(int errorCode, String errorDesc)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 
PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId)542         private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
543             mWakeLock = wakeLock;
544             mStartId = startId;
545         }
546 
packageDeleted(String packageName, int returnCode)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 
buildNotification(final String packageName, final String title)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 
getLabelAndUpdateNotification(String packageName, String title)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