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