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 finishService(null, startId); 133 return START_NOT_STICKY; 134 } 135 136 if (intent == null) { 137 Log.w(TAG, "Got null intent."); 138 finishService(null, 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 finishService(null, startId); 150 return START_NOT_STICKY; 151 } 152 final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri); 153 if (packageName == null) { 154 Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri); 155 finishService(null, startId); 156 return START_NOT_STICKY; 157 } 158 159 PowerManager.WakeLock lock = getLock(this.getApplicationContext()); 160 if (!lock.isHeld()) { 161 lock.acquire(); 162 } 163 164 Bundle intentBundle = intent.getExtras(); 165 if (intentBundle == null) { 166 intentBundle = new Bundle(); 167 } 168 WearPackageArgs.setStartId(intentBundle, startId); 169 WearPackageArgs.setPackageName(intentBundle, packageName); 170 String notifTitle; 171 if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) { 172 Message msg = mServiceHandler.obtainMessage(START_INSTALL); 173 msg.setData(intentBundle); 174 mServiceHandler.sendMessage(msg); 175 notifTitle = getString(R.string.installing); 176 } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) { 177 Message msg = mServiceHandler.obtainMessage(START_UNINSTALL); 178 msg.setData(intentBundle); 179 mServiceHandler.sendMessage(msg); 180 notifTitle = getString(R.string.uninstalling); 181 } else { 182 Log.e(TAG, "Unknown action : " + intent.getAction()); 183 finishService(null, startId); 184 return START_NOT_STICKY; 185 } 186 Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle); 187 startForeground(notifPair.first, notifPair.second); 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 getLabelAndUpdateNotification(packageName, 260 getString(R.string.installing_app, pkg.applicationInfo.loadLabel(pm))); 261 262 List<String> wearablePerms = pkg.requestedPermissions; 263 264 // Log if the installed pkg has a higher version number. 265 if (existingPkgInfo != null) { 266 if (existingPkgInfo.versionCode == pkg.mVersionCode) { 267 if (skipIfSameVersion) { 268 Log.w(TAG, "Version number (" + pkg.mVersionCode + 269 ") of new app is equal to existing app for " + packageName + 270 "; not installing due to versionCheck"); 271 return; 272 } else { 273 Log.w(TAG, "Version number of new app (" + pkg.mVersionCode + 274 ") is equal to existing app for " + packageName); 275 } 276 } else if (existingPkgInfo.versionCode > pkg.mVersionCode) { 277 if (skipIfLowerVersion) { 278 // Starting in Feldspar, we are not going to allow downgrades of any app. 279 Log.w(TAG, "Version number of new app (" + pkg.mVersionCode + 280 ") is lower than existing app ( " + existingPkgInfo.versionCode + 281 ") for " + packageName + "; not installing due to versionCheck"); 282 return; 283 } else { 284 Log.w(TAG, "Version number of new app (" + pkg.mVersionCode + 285 ") is lower than existing app ( " + existingPkgInfo.versionCode + 286 ") for " + packageName); 287 } 288 } 289 290 // Following the Android Phone model, we should only check for permissions for any 291 // newly defined perms. 292 if (existingPkgInfo.requestedPermissions != null) { 293 for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) { 294 // If the permission is granted, then we will not ask to request it again. 295 if ((existingPkgInfo.requestedPermissionsFlags[i] & 296 PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 297 if (Log.isLoggable(TAG, Log.DEBUG)) { 298 Log.d(TAG, existingPkgInfo.requestedPermissions[i] + 299 " is already granted for " + packageName); 300 } 301 wearablePerms.remove(existingPkgInfo.requestedPermissions[i]); 302 } 303 } 304 } 305 } 306 307 // Check that the wearable has all the features. 308 boolean hasAllFeatures = true; 309 if (pkg.reqFeatures != null) { 310 for (FeatureInfo feature : pkg.reqFeatures) { 311 if (feature.name != null && !pm.hasSystemFeature(feature.name) && 312 (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) { 313 Log.e(TAG, "Wearable does not have required feature: " + feature + 314 " for " + packageName); 315 hasAllFeatures = false; 316 } 317 } 318 } 319 320 if (!hasAllFeatures) { 321 return; 322 } 323 324 // Check permissions on both the new wearable package and also on the already installed 325 // wearable package. 326 // If the app is targeting API level 23, we will also start a service in ClockworkHome 327 // which will ultimately prompt the user to accept/reject permissions. 328 if (checkPerms && !checkPermissions(pkg, companionSdkVersion, companionDeviceVersion, 329 permUri, wearablePerms, tempFile)) { 330 Log.w(TAG, "Wearable does not have enough permissions."); 331 return; 332 } 333 334 // Finally install the package. 335 ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r"); 336 PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd, 337 new PackageInstallListener(this, lock, startId, packageName)); 338 339 messageSent = true; 340 Log.i(TAG, "Sent installation request for " + packageName); 341 } catch (FileNotFoundException e) { 342 Log.e(TAG, "Could not find the file with URI " + assetUri, e); 343 } finally { 344 if (!messageSent) { 345 // Some error happened. If the message has been sent, we can wait for the observer 346 // which will finish the service. 347 if (tempFile != null) { 348 tempFile.delete(); 349 } 350 finishService(lock, startId); 351 } 352 } 353 } 354 355 // TODO: This was left using the old PackageManager API due to the fact that this code is being 356 // deprecated and that we need the bare minimum to continue working moving forward 357 // If this code is used as reference, this logic should be reworked to use the new 358 // PackageInstaller APIs similar to how installPackage was reworked uninstallPackage(Bundle argsBundle)359 private void uninstallPackage(Bundle argsBundle) { 360 int startId = WearPackageArgs.getStartId(argsBundle); 361 final String packageName = WearPackageArgs.getPackageName(argsBundle); 362 363 PowerManager.WakeLock lock = getLock(this.getApplicationContext()); 364 final PackageManager pm = getPackageManager(); 365 try { 366 PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0); 367 getLabelAndUpdateNotification(packageName, 368 getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm))); 369 370 // Found package, send uninstall request. 371 pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId), 372 PackageManager.DELETE_ALL_USERS); 373 374 Log.i(TAG, "Sent delete request for " + packageName); 375 } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) { 376 // Couldn't find the package, no need to call uninstall. 377 Log.w(TAG, "Could not find package, not deleting " + packageName, e); 378 finishService(lock, startId); 379 } 380 } 381 checkPermissions(PackageParser.Package pkg, int companionSdkVersion, int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, File apkFile)382 private boolean checkPermissions(PackageParser.Package pkg, int companionSdkVersion, 383 int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, 384 File apkFile) { 385 // Assumption: We are running on Android O. 386 // If the Phone App is targeting M, all permissions may not have been granted to the phone 387 // app. If the Wear App is then not targeting M, there may be permissions that are not 388 // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear 389 // app. 390 if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { 391 // Install the app if Wear App is ready for the new perms model. 392 return true; 393 } 394 395 if (!doesWearHaveUngrantedPerms(pkg.packageName, permUri, wearablePermissions)) { 396 // All permissions requested by the watch are already granted on the phone, no need 397 // to do anything. 398 return true; 399 } 400 401 // Log an error if Wear is targeting < 23 and phone is targeting >= 23. 402 if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) { 403 Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if " 404 + "phone app is targeting at least 23, will continue."); 405 } 406 407 return false; 408 } 409 410 /** 411 * Given a {@string packageName} corresponding to a phone app, query the provider for all the 412 * perms that are granted. 413 * 414 * @return true if the Wear App has any perms that have not been granted yet on the phone side. 415 * @return true if there is any error cases. 416 */ doesWearHaveUngrantedPerms(String packageName, Uri permUri, List<String> wearablePermissions)417 private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri, 418 List<String> wearablePermissions) { 419 if (permUri == null) { 420 Log.e(TAG, "Permission URI is null"); 421 // Pretend there is an ungranted permission to avoid installing for error cases. 422 return true; 423 } 424 Cursor permCursor = getContentResolver().query(permUri, null, null, null, null); 425 if (permCursor == null) { 426 Log.e(TAG, "Could not get the cursor for the permissions"); 427 // Pretend there is an ungranted permission to avoid installing for error cases. 428 return true; 429 } 430 431 Set<String> grantedPerms = new HashSet<>(); 432 Set<String> ungrantedPerms = new HashSet<>(); 433 while(permCursor.moveToNext()) { 434 // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and 435 // verify their types. 436 if (permCursor.getColumnCount() == 2 437 && Cursor.FIELD_TYPE_STRING == permCursor.getType(0) 438 && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) { 439 String perm = permCursor.getString(0); 440 Integer granted = permCursor.getInt(1); 441 if (granted == 1) { 442 grantedPerms.add(perm); 443 } else { 444 ungrantedPerms.add(perm); 445 } 446 } 447 } 448 permCursor.close(); 449 450 boolean hasUngrantedPerm = false; 451 for (String wearablePerm : wearablePermissions) { 452 if (!grantedPerms.contains(wearablePerm)) { 453 hasUngrantedPerm = true; 454 if (!ungrantedPerms.contains(wearablePerm)) { 455 // This is an error condition. This means that the wearable has permissions that 456 // are not even declared in its host app. This is a developer error. 457 Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm 458 + "\" that is not defined in the host application's manifest."); 459 } else { 460 Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm + 461 "\" that is not granted in the host application."); 462 } 463 } 464 } 465 return hasUngrantedPerm; 466 } 467 finishService(PowerManager.WakeLock lock, int startId)468 private void finishService(PowerManager.WakeLock lock, int startId) { 469 if (lock != null && lock.isHeld()) { 470 lock.release(); 471 } 472 stopSelf(startId); 473 } 474 getLock(Context context)475 private synchronized PowerManager.WakeLock getLock(Context context) { 476 if (lockStatic == null) { 477 PowerManager mgr = 478 (PowerManager) context.getSystemService(Context.POWER_SERVICE); 479 lockStatic = mgr.newWakeLock( 480 PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName()); 481 lockStatic.setReferenceCounted(true); 482 } 483 return lockStatic; 484 } 485 486 private class PackageInstallListener implements PackageInstallerImpl.InstallListener { 487 private Context mContext; 488 private PowerManager.WakeLock mWakeLock; 489 private int mStartId; 490 private String mApplicationPackageName; PackageInstallListener(Context context, PowerManager.WakeLock wakeLock, int startId, String applicationPackageName)491 private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock, 492 int startId, String applicationPackageName) { 493 mContext = context; 494 mWakeLock = wakeLock; 495 mStartId = startId; 496 mApplicationPackageName = applicationPackageName; 497 } 498 499 @Override installBeginning()500 public void installBeginning() { 501 Log.i(TAG, "Package " + mApplicationPackageName + " is being installed."); 502 } 503 504 @Override installSucceeded()505 public void installSucceeded() { 506 try { 507 Log.i(TAG, "Package " + mApplicationPackageName + " was installed."); 508 509 // Delete tempFile from the file system. 510 File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName); 511 if (tempFile != null) { 512 tempFile.delete(); 513 } 514 } finally { 515 finishService(mWakeLock, mStartId); 516 } 517 } 518 519 @Override installFailed(int errorCode, String errorDesc)520 public void installFailed(int errorCode, String errorDesc) { 521 Log.e(TAG, "Package install failed " + mApplicationPackageName 522 + ", errorCode " + errorCode); 523 finishService(mWakeLock, mStartId); 524 } 525 } 526 527 private class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 528 private PowerManager.WakeLock mWakeLock; 529 private int mStartId; 530 PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId)531 private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) { 532 mWakeLock = wakeLock; 533 mStartId = startId; 534 } 535 packageDeleted(String packageName, int returnCode)536 public void packageDeleted(String packageName, int returnCode) { 537 try { 538 if (returnCode >= 0) { 539 Log.i(TAG, "Package " + packageName + " was uninstalled."); 540 } else { 541 Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " + 542 returnCode); 543 } 544 } finally { 545 finishService(mWakeLock, mStartId); 546 } 547 } 548 } 549 buildNotification(final String packageName, final String title)550 private synchronized Pair<Integer, Notification> buildNotification(final String packageName, 551 final String title) { 552 int notifId; 553 if (mNotifIdMap.containsKey(packageName)) { 554 notifId = mNotifIdMap.get(packageName); 555 } else { 556 notifId = mInstallNotificationId++; 557 mNotifIdMap.put(packageName, notifId); 558 } 559 560 if (mNotificationChannel == null) { 561 mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL, 562 getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN); 563 NotificationManager notificationManager = getSystemService(NotificationManager.class); 564 notificationManager.createNotificationChannel(mNotificationChannel); 565 } 566 return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL) 567 .setSmallIcon(R.drawable.ic_file_download) 568 .setContentTitle(title) 569 .build()); 570 } 571 getLabelAndUpdateNotification(String packageName, String title)572 private void getLabelAndUpdateNotification(String packageName, String title) { 573 // Update notification since we have a label now. 574 NotificationManager notificationManager = getSystemService(NotificationManager.class); 575 Pair<Integer, Notification> notifPair = buildNotification(packageName, title); 576 notificationManager.notify(notifPair.first, notifPair.second); 577 } 578 } 579