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