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