PackageInstallerService.java revision f8bb2445ff28d64d12d81d91539bb419f69e7874
1/* 2 * Copyright (C) 2014 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 17package com.android.server.pm; 18 19import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 20import static org.xmlpull.v1.XmlPullParser.START_TAG; 21 22import android.Manifest; 23import android.app.ActivityManager; 24import android.app.AppGlobals; 25import android.app.AppOpsManager; 26import android.app.Notification; 27import android.app.NotificationManager; 28import android.app.PackageDeleteObserver; 29import android.app.PackageInstallObserver; 30import android.app.admin.DevicePolicyManager; 31import android.content.Context; 32import android.content.Intent; 33import android.content.IntentSender; 34import android.content.IntentSender.SendIntentException; 35import android.content.pm.IPackageInstaller; 36import android.content.pm.IPackageInstallerCallback; 37import android.content.pm.IPackageInstallerSession; 38import android.content.pm.PackageInfo; 39import android.content.pm.PackageInstaller; 40import android.content.pm.PackageInstaller.SessionInfo; 41import android.content.pm.PackageInstaller.SessionParams; 42import android.content.pm.PackageManager; 43import android.content.pm.ParceledListSlice; 44import android.content.pm.VersionedPackage; 45import android.graphics.Bitmap; 46import android.net.Uri; 47import android.os.Binder; 48import android.os.Bundle; 49import android.os.Environment; 50import android.os.Handler; 51import android.os.HandlerThread; 52import android.os.Looper; 53import android.os.Message; 54import android.os.Process; 55import android.os.RemoteCallbackList; 56import android.os.RemoteException; 57import android.os.SELinux; 58import android.os.UserHandle; 59import android.os.UserManager; 60import android.os.storage.StorageManager; 61import android.system.ErrnoException; 62import android.system.Os; 63import android.text.TextUtils; 64import android.text.format.DateUtils; 65import android.util.ArraySet; 66import android.util.AtomicFile; 67import android.util.ExceptionUtils; 68import android.util.Slog; 69import android.util.SparseArray; 70import android.util.SparseBooleanArray; 71import android.util.SparseIntArray; 72import android.util.Xml; 73 74import com.android.internal.R; 75import com.android.internal.annotations.GuardedBy; 76import com.android.internal.content.PackageHelper; 77import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 78import com.android.internal.notification.SystemNotificationChannels; 79import com.android.internal.util.FastXmlSerializer; 80import com.android.internal.util.ImageUtils; 81import com.android.internal.util.IndentingPrintWriter; 82import com.android.server.IoThread; 83 84import libcore.io.IoUtils; 85 86import org.xmlpull.v1.XmlPullParser; 87import org.xmlpull.v1.XmlPullParserException; 88import org.xmlpull.v1.XmlSerializer; 89 90import java.io.CharArrayWriter; 91import java.io.File; 92import java.io.FileInputStream; 93import java.io.FileNotFoundException; 94import java.io.FileOutputStream; 95import java.io.FilenameFilter; 96import java.io.IOException; 97import java.nio.charset.StandardCharsets; 98import java.security.SecureRandom; 99import java.util.ArrayList; 100import java.util.Collections; 101import java.util.List; 102import java.util.Objects; 103import java.util.Random; 104 105public class PackageInstallerService extends IPackageInstaller.Stub { 106 private static final String TAG = "PackageInstaller"; 107 private static final boolean LOGD = false; 108 109 // TODO: remove outstanding sessions when installer package goes away 110 // TODO: notify listeners in other users when package has been installed there 111 // TODO: purge expired sessions periodically in addition to at reboot 112 113 /** XML constants used in {@link #mSessionsFile} */ 114 private static final String TAG_SESSIONS = "sessions"; 115 116 /** Automatically destroy sessions older than this */ 117 private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS; 118 /** Upper bound on number of active sessions for a UID */ 119 private static final long MAX_ACTIVE_SESSIONS = 1024; 120 /** Upper bound on number of historical sessions for a UID */ 121 private static final long MAX_HISTORICAL_SESSIONS = 1048576; 122 123 private final Context mContext; 124 private final PackageManagerService mPm; 125 126 private AppOpsManager mAppOps; 127 128 private final HandlerThread mInstallThread; 129 private final Handler mInstallHandler; 130 131 private final Callbacks mCallbacks; 132 133 /** 134 * File storing persisted {@link #mSessions} metadata. 135 */ 136 private final AtomicFile mSessionsFile; 137 138 /** 139 * Directory storing persisted {@link #mSessions} metadata which is too 140 * heavy to store directly in {@link #mSessionsFile}. 141 */ 142 private final File mSessionsDir; 143 144 private final InternalCallback mInternalCallback = new InternalCallback(); 145 146 /** 147 * Used for generating session IDs. Since this is created at boot time, 148 * normal random might be predictable. 149 */ 150 private final Random mRandom = new SecureRandom(); 151 152 /** All sessions allocated */ 153 @GuardedBy("mSessions") 154 private final SparseBooleanArray mAllocatedSessions = new SparseBooleanArray(); 155 156 @GuardedBy("mSessions") 157 private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>(); 158 159 /** Historical sessions kept around for debugging purposes */ 160 @GuardedBy("mSessions") 161 private final List<String> mHistoricalSessions = new ArrayList<>(); 162 163 @GuardedBy("mSessions") 164 private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray(); 165 166 /** Sessions allocated to legacy users */ 167 @GuardedBy("mSessions") 168 private final SparseBooleanArray mLegacySessions = new SparseBooleanArray(); 169 170 private static final FilenameFilter sStageFilter = new FilenameFilter() { 171 @Override 172 public boolean accept(File dir, String name) { 173 return isStageName(name); 174 } 175 }; 176 177 public PackageInstallerService(Context context, PackageManagerService pm) { 178 mContext = context; 179 mPm = pm; 180 181 mInstallThread = new HandlerThread(TAG); 182 mInstallThread.start(); 183 184 mInstallHandler = new Handler(mInstallThread.getLooper()); 185 186 mCallbacks = new Callbacks(mInstallThread.getLooper()); 187 188 mSessionsFile = new AtomicFile( 189 new File(Environment.getDataSystemDirectory(), "install_sessions.xml")); 190 mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); 191 mSessionsDir.mkdirs(); 192 } 193 194 public void systemReady() { 195 mAppOps = mContext.getSystemService(AppOpsManager.class); 196 197 synchronized (mSessions) { 198 readSessionsLocked(); 199 200 reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/); 201 reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/); 202 203 final ArraySet<File> unclaimedIcons = newArraySet( 204 mSessionsDir.listFiles()); 205 206 // Ignore stages and icons claimed by active sessions 207 for (int i = 0; i < mSessions.size(); i++) { 208 final PackageInstallerSession session = mSessions.valueAt(i); 209 unclaimedIcons.remove(buildAppIconFile(session.sessionId)); 210 } 211 212 // Clean up orphaned icons 213 for (File icon : unclaimedIcons) { 214 Slog.w(TAG, "Deleting orphan icon " + icon); 215 icon.delete(); 216 } 217 } 218 } 219 220 private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) { 221 final File stagingDir = buildStagingDir(volumeUuid, isEphemeral); 222 final ArraySet<File> unclaimedStages = newArraySet( 223 stagingDir.listFiles(sStageFilter)); 224 225 // Ignore stages claimed by active sessions 226 for (int i = 0; i < mSessions.size(); i++) { 227 final PackageInstallerSession session = mSessions.valueAt(i); 228 unclaimedStages.remove(session.stageDir); 229 } 230 231 // Clean up orphaned staging directories 232 for (File stage : unclaimedStages) { 233 Slog.w(TAG, "Deleting orphan stage " + stage); 234 synchronized (mPm.mInstallLock) { 235 mPm.removeCodePathLI(stage); 236 } 237 } 238 } 239 240 public void onPrivateVolumeMounted(String volumeUuid) { 241 synchronized (mSessions) { 242 reconcileStagesLocked(volumeUuid, false /*isInstant*/); 243 } 244 } 245 246 public static boolean isStageName(String name) { 247 final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); 248 final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); 249 final boolean isLegacyContainer = name.startsWith("smdl2tmp"); 250 return isFile || isContainer || isLegacyContainer; 251 } 252 253 @Deprecated 254 public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException { 255 synchronized (mSessions) { 256 try { 257 final int sessionId = allocateSessionIdLocked(); 258 mLegacySessions.put(sessionId, true); 259 final File stageDir = buildStageDir(volumeUuid, sessionId, isEphemeral); 260 prepareStageDir(stageDir); 261 return stageDir; 262 } catch (IllegalStateException e) { 263 throw new IOException(e); 264 } 265 } 266 } 267 268 @Deprecated 269 public String allocateExternalStageCidLegacy() { 270 synchronized (mSessions) { 271 final int sessionId = allocateSessionIdLocked(); 272 mLegacySessions.put(sessionId, true); 273 return "smdl" + sessionId + ".tmp"; 274 } 275 } 276 277 private void readSessionsLocked() { 278 if (LOGD) Slog.v(TAG, "readSessionsLocked()"); 279 280 mSessions.clear(); 281 282 FileInputStream fis = null; 283 try { 284 fis = mSessionsFile.openRead(); 285 final XmlPullParser in = Xml.newPullParser(); 286 in.setInput(fis, StandardCharsets.UTF_8.name()); 287 288 int type; 289 while ((type = in.next()) != END_DOCUMENT) { 290 if (type == START_TAG) { 291 final String tag = in.getName(); 292 if (PackageInstallerSession.TAG_SESSION.equals(tag)) { 293 final PackageInstallerSession session; 294 try { 295 session = PackageInstallerSession.readFromXml(in, mInternalCallback, 296 mContext, mPm, mInstallThread.getLooper(), mSessionsDir); 297 } catch (Exception e) { 298 Slog.e(TAG, "Could not read session", e); 299 continue; 300 } 301 302 final long age = System.currentTimeMillis() - session.createdMillis; 303 304 final boolean valid; 305 if (age >= MAX_AGE_MILLIS) { 306 Slog.w(TAG, "Abandoning old session first created at " 307 + session.createdMillis); 308 valid = false; 309 } else { 310 valid = true; 311 } 312 313 if (valid) { 314 mSessions.put(session.sessionId, session); 315 } else { 316 // Since this is early during boot we don't send 317 // any observer events about the session, but we 318 // keep details around for dumpsys. 319 addHistoricalSessionLocked(session); 320 } 321 mAllocatedSessions.put(session.sessionId, true); 322 } 323 } 324 } 325 } catch (FileNotFoundException e) { 326 // Missing sessions are okay, probably first boot 327 } catch (IOException | XmlPullParserException e) { 328 Slog.wtf(TAG, "Failed reading install sessions", e); 329 } finally { 330 IoUtils.closeQuietly(fis); 331 } 332 } 333 334 private void addHistoricalSessionLocked(PackageInstallerSession session) { 335 CharArrayWriter writer = new CharArrayWriter(); 336 IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 337 session.dump(pw); 338 mHistoricalSessions.add(writer.toString()); 339 340 int installerUid = session.getInstallerUid(); 341 // Increment the number of sessions by this installerUid. 342 mHistoricalSessionsByInstaller.put(installerUid, 343 mHistoricalSessionsByInstaller.get(installerUid) + 1); 344 } 345 346 private void writeSessionsLocked() { 347 if (LOGD) Slog.v(TAG, "writeSessionsLocked()"); 348 349 FileOutputStream fos = null; 350 try { 351 fos = mSessionsFile.startWrite(); 352 353 XmlSerializer out = new FastXmlSerializer(); 354 out.setOutput(fos, StandardCharsets.UTF_8.name()); 355 out.startDocument(null, true); 356 out.startTag(null, TAG_SESSIONS); 357 final int size = mSessions.size(); 358 for (int i = 0; i < size; i++) { 359 final PackageInstallerSession session = mSessions.valueAt(i); 360 session.write(out, mSessionsDir); 361 } 362 out.endTag(null, TAG_SESSIONS); 363 out.endDocument(); 364 365 mSessionsFile.finishWrite(fos); 366 } catch (IOException e) { 367 if (fos != null) { 368 mSessionsFile.failWrite(fos); 369 } 370 } 371 } 372 373 private File buildAppIconFile(int sessionId) { 374 return new File(mSessionsDir, "app_icon." + sessionId + ".png"); 375 } 376 377 private void writeSessionsAsync() { 378 IoThread.getHandler().post(new Runnable() { 379 @Override 380 public void run() { 381 synchronized (mSessions) { 382 writeSessionsLocked(); 383 } 384 } 385 }); 386 } 387 388 @Override 389 public int createSession(SessionParams params, String installerPackageName, int userId) { 390 try { 391 return createSessionInternal(params, installerPackageName, userId); 392 } catch (IOException e) { 393 throw ExceptionUtils.wrap(e); 394 } 395 } 396 397 private int createSessionInternal(SessionParams params, String installerPackageName, int userId) 398 throws IOException { 399 final int callingUid = Binder.getCallingUid(); 400 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession"); 401 402 if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { 403 throw new SecurityException("User restriction prevents installing"); 404 } 405 406 if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { 407 params.installFlags |= PackageManager.INSTALL_FROM_ADB; 408 409 } else { 410 mAppOps.checkPackage(callingUid, installerPackageName); 411 412 params.installFlags &= ~PackageManager.INSTALL_FROM_ADB; 413 params.installFlags &= ~PackageManager.INSTALL_ALL_USERS; 414 params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; 415 if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0 416 && !mPm.isCallerVerifier(callingUid)) { 417 params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD; 418 } 419 } 420 421 // Only system components can circumvent runtime permissions when installing. 422 if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 423 && mContext.checkCallingOrSelfPermission(Manifest.permission 424 .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { 425 throw new SecurityException("You need the " 426 + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " 427 + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); 428 } 429 430 if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0 431 || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 432 throw new IllegalArgumentException( 433 "New installs into ASEC containers no longer supported"); 434 } 435 436 // Defensively resize giant app icons 437 if (params.appIcon != null) { 438 final ActivityManager am = (ActivityManager) mContext.getSystemService( 439 Context.ACTIVITY_SERVICE); 440 final int iconSize = am.getLauncherLargeIconSize(); 441 if ((params.appIcon.getWidth() > iconSize * 2) 442 || (params.appIcon.getHeight() > iconSize * 2)) { 443 params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize, 444 true); 445 } 446 } 447 448 switch (params.mode) { 449 case SessionParams.MODE_FULL_INSTALL: 450 case SessionParams.MODE_INHERIT_EXISTING: 451 break; 452 default: 453 throw new IllegalArgumentException("Invalid install mode: " + params.mode); 454 } 455 456 // If caller requested explicit location, sanity check it, otherwise 457 // resolve the best internal or adopted location. 458 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 459 if (!PackageHelper.fitsOnInternal(mContext, params)) { 460 throw new IOException("No suitable internal storage available"); 461 } 462 463 } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 464 if (!PackageHelper.fitsOnExternal(mContext, params)) { 465 throw new IOException("No suitable external storage available"); 466 } 467 468 } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { 469 // For now, installs to adopted media are treated as internal from 470 // an install flag point-of-view. 471 params.setInstallFlagsInternal(); 472 473 } else { 474 // For now, installs to adopted media are treated as internal from 475 // an install flag point-of-view. 476 params.setInstallFlagsInternal(); 477 478 // Resolve best location for install, based on combination of 479 // requested install flags, delta size, and manifest settings. 480 final long ident = Binder.clearCallingIdentity(); 481 try { 482 params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params); 483 } finally { 484 Binder.restoreCallingIdentity(ident); 485 } 486 } 487 488 final int sessionId; 489 final PackageInstallerSession session; 490 synchronized (mSessions) { 491 // Sanity check that installer isn't going crazy 492 final int activeCount = getSessionCount(mSessions, callingUid); 493 if (activeCount >= MAX_ACTIVE_SESSIONS) { 494 throw new IllegalStateException( 495 "Too many active sessions for UID " + callingUid); 496 } 497 final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid); 498 if (historicalCount >= MAX_HISTORICAL_SESSIONS) { 499 throw new IllegalStateException( 500 "Too many historical sessions for UID " + callingUid); 501 } 502 503 sessionId = allocateSessionIdLocked(); 504 } 505 506 final long createdMillis = System.currentTimeMillis(); 507 // We're staging to exactly one location 508 File stageDir = null; 509 String stageCid = null; 510 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 511 final boolean isInstant = 512 (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; 513 stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant); 514 } else { 515 stageCid = buildExternalStageCid(sessionId); 516 } 517 518 session = new PackageInstallerSession(mInternalCallback, mContext, mPm, 519 mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid, 520 params, createdMillis, stageDir, stageCid, false, false); 521 522 synchronized (mSessions) { 523 mSessions.put(sessionId, session); 524 } 525 526 mCallbacks.notifySessionCreated(session.sessionId, session.userId); 527 writeSessionsAsync(); 528 return sessionId; 529 } 530 531 @Override 532 public void updateSessionAppIcon(int sessionId, Bitmap appIcon) { 533 synchronized (mSessions) { 534 final PackageInstallerSession session = mSessions.get(sessionId); 535 if (session == null || !isCallingUidOwner(session)) { 536 throw new SecurityException("Caller has no access to session " + sessionId); 537 } 538 539 // Defensively resize giant app icons 540 if (appIcon != null) { 541 final ActivityManager am = (ActivityManager) mContext.getSystemService( 542 Context.ACTIVITY_SERVICE); 543 final int iconSize = am.getLauncherLargeIconSize(); 544 if ((appIcon.getWidth() > iconSize * 2) 545 || (appIcon.getHeight() > iconSize * 2)) { 546 appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true); 547 } 548 } 549 550 session.params.appIcon = appIcon; 551 session.params.appIconLastModified = -1; 552 553 mInternalCallback.onSessionBadgingChanged(session); 554 } 555 } 556 557 @Override 558 public void updateSessionAppLabel(int sessionId, String appLabel) { 559 synchronized (mSessions) { 560 final PackageInstallerSession session = mSessions.get(sessionId); 561 if (session == null || !isCallingUidOwner(session)) { 562 throw new SecurityException("Caller has no access to session " + sessionId); 563 } 564 session.params.appLabel = appLabel; 565 mInternalCallback.onSessionBadgingChanged(session); 566 } 567 } 568 569 @Override 570 public void abandonSession(int sessionId) { 571 synchronized (mSessions) { 572 final PackageInstallerSession session = mSessions.get(sessionId); 573 if (session == null || !isCallingUidOwner(session)) { 574 throw new SecurityException("Caller has no access to session " + sessionId); 575 } 576 session.abandon(); 577 } 578 } 579 580 @Override 581 public IPackageInstallerSession openSession(int sessionId) { 582 try { 583 return openSessionInternal(sessionId); 584 } catch (IOException e) { 585 throw ExceptionUtils.wrap(e); 586 } 587 } 588 589 private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { 590 synchronized (mSessions) { 591 final PackageInstallerSession session = mSessions.get(sessionId); 592 if (session == null || !isCallingUidOwner(session)) { 593 throw new SecurityException("Caller has no access to session " + sessionId); 594 } 595 session.open(); 596 return session; 597 } 598 } 599 600 private int allocateSessionIdLocked() { 601 int n = 0; 602 int sessionId; 603 do { 604 sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 605 if (!mAllocatedSessions.get(sessionId, false)) { 606 mAllocatedSessions.put(sessionId, true); 607 return sessionId; 608 } 609 } while (n++ < 32); 610 611 throw new IllegalStateException("Failed to allocate session ID"); 612 } 613 614 private File buildStagingDir(String volumeUuid, boolean isEphemeral) { 615 return Environment.getDataAppDirectory(volumeUuid); 616 } 617 618 private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) { 619 final File stagingDir = buildStagingDir(volumeUuid, isEphemeral); 620 return new File(stagingDir, "vmdl" + sessionId + ".tmp"); 621 } 622 623 static void prepareStageDir(File stageDir) throws IOException { 624 if (stageDir.exists()) { 625 throw new IOException("Session dir already exists: " + stageDir); 626 } 627 628 try { 629 Os.mkdir(stageDir.getAbsolutePath(), 0755); 630 Os.chmod(stageDir.getAbsolutePath(), 0755); 631 } catch (ErrnoException e) { 632 // This purposefully throws if directory already exists 633 throw new IOException("Failed to prepare session dir: " + stageDir, e); 634 } 635 636 if (!SELinux.restorecon(stageDir)) { 637 throw new IOException("Failed to restorecon session dir: " + stageDir); 638 } 639 } 640 641 private String buildExternalStageCid(int sessionId) { 642 return "smdl" + sessionId + ".tmp"; 643 } 644 645 @Override 646 public SessionInfo getSessionInfo(int sessionId) { 647 synchronized (mSessions) { 648 final PackageInstallerSession session = mSessions.get(sessionId); 649 return session != null ? session.generateInfo() : null; 650 } 651 } 652 653 @Override 654 public ParceledListSlice<SessionInfo> getAllSessions(int userId) { 655 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions"); 656 657 final List<SessionInfo> result = new ArrayList<>(); 658 synchronized (mSessions) { 659 for (int i = 0; i < mSessions.size(); i++) { 660 final PackageInstallerSession session = mSessions.valueAt(i); 661 if (session.userId == userId) { 662 result.add(session.generateInfo(false)); 663 } 664 } 665 } 666 return new ParceledListSlice<>(result); 667 } 668 669 @Override 670 public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) { 671 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions"); 672 mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName); 673 674 final List<SessionInfo> result = new ArrayList<>(); 675 synchronized (mSessions) { 676 for (int i = 0; i < mSessions.size(); i++) { 677 final PackageInstallerSession session = mSessions.valueAt(i); 678 679 SessionInfo info = session.generateInfo(false); 680 if (Objects.equals(info.getInstallerPackageName(), installerPackageName) 681 && session.userId == userId) { 682 result.add(info); 683 } 684 } 685 } 686 return new ParceledListSlice<>(result); 687 } 688 689 @Override 690 public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags, 691 IntentSender statusReceiver, int userId) throws RemoteException { 692 final int callingUid = Binder.getCallingUid(); 693 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); 694 if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) { 695 mAppOps.checkPackage(callingUid, callerPackageName); 696 } 697 698 // Check whether the caller is device owner, in which case we do it silently. 699 DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( 700 Context.DEVICE_POLICY_SERVICE); 701 boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser( 702 callerPackageName); 703 704 final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, 705 statusReceiver, versionedPackage.getPackageName(), isDeviceOwner, userId); 706 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) 707 == PackageManager.PERMISSION_GRANTED) { 708 // Sweet, call straight through! 709 mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); 710 } else if (isDeviceOwner) { 711 // Allow the DeviceOwner to silently delete packages 712 // Need to clear the calling identity to get DELETE_PACKAGES permission 713 long ident = Binder.clearCallingIdentity(); 714 try { 715 mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); 716 } finally { 717 Binder.restoreCallingIdentity(ident); 718 } 719 } else { 720 // Take a short detour to confirm with user 721 final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); 722 intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null)); 723 intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder()); 724 adapter.onUserActionRequired(intent); 725 } 726 } 727 728 @Override 729 public void setPermissionsResult(int sessionId, boolean accepted) { 730 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG); 731 732 synchronized (mSessions) { 733 PackageInstallerSession session = mSessions.get(sessionId); 734 if (session != null) { 735 session.setPermissionsResult(accepted); 736 } 737 } 738 } 739 740 @Override 741 public void registerCallback(IPackageInstallerCallback callback, int userId) { 742 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback"); 743 mCallbacks.register(callback, userId); 744 } 745 746 @Override 747 public void unregisterCallback(IPackageInstallerCallback callback) { 748 mCallbacks.unregister(callback); 749 } 750 751 private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, 752 int installerUid) { 753 int count = 0; 754 final int size = sessions.size(); 755 for (int i = 0; i < size; i++) { 756 final PackageInstallerSession session = sessions.valueAt(i); 757 if (session.getInstallerUid() == installerUid) { 758 count++; 759 } 760 } 761 return count; 762 } 763 764 private boolean isCallingUidOwner(PackageInstallerSession session) { 765 final int callingUid = Binder.getCallingUid(); 766 if (callingUid == Process.ROOT_UID) { 767 return true; 768 } else { 769 return (session != null) && (callingUid == session.getInstallerUid()); 770 } 771 } 772 773 static class PackageDeleteObserverAdapter extends PackageDeleteObserver { 774 private final Context mContext; 775 private final IntentSender mTarget; 776 private final String mPackageName; 777 private final Notification mNotification; 778 779 public PackageDeleteObserverAdapter(Context context, IntentSender target, 780 String packageName, boolean showNotification, int userId) { 781 mContext = context; 782 mTarget = target; 783 mPackageName = packageName; 784 if (showNotification) { 785 mNotification = buildSuccessNotification(mContext, 786 mContext.getResources().getString(R.string.package_deleted_device_owner), 787 packageName, 788 userId); 789 } else { 790 mNotification = null; 791 } 792 } 793 794 @Override 795 public void onUserActionRequired(Intent intent) { 796 final Intent fillIn = new Intent(); 797 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); 798 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 799 PackageInstaller.STATUS_PENDING_USER_ACTION); 800 fillIn.putExtra(Intent.EXTRA_INTENT, intent); 801 try { 802 mTarget.sendIntent(mContext, 0, fillIn, null, null); 803 } catch (SendIntentException ignored) { 804 } 805 } 806 807 @Override 808 public void onPackageDeleted(String basePackageName, int returnCode, String msg) { 809 if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) { 810 NotificationManager notificationManager = (NotificationManager) 811 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 812 notificationManager.notify(basePackageName, 813 SystemMessage.NOTE_PACKAGE_STATE, 814 mNotification); 815 } 816 final Intent fillIn = new Intent(); 817 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); 818 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 819 PackageManager.deleteStatusToPublicStatus(returnCode)); 820 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, 821 PackageManager.deleteStatusToString(returnCode, msg)); 822 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); 823 try { 824 mTarget.sendIntent(mContext, 0, fillIn, null, null); 825 } catch (SendIntentException ignored) { 826 } 827 } 828 } 829 830 static class PackageInstallObserverAdapter extends PackageInstallObserver { 831 private final Context mContext; 832 private final IntentSender mTarget; 833 private final int mSessionId; 834 private final boolean mShowNotification; 835 private final int mUserId; 836 837 public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId, 838 boolean showNotification, int userId) { 839 mContext = context; 840 mTarget = target; 841 mSessionId = sessionId; 842 mShowNotification = showNotification; 843 mUserId = userId; 844 } 845 846 @Override 847 public void onUserActionRequired(Intent intent) { 848 final Intent fillIn = new Intent(); 849 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); 850 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 851 PackageInstaller.STATUS_PENDING_USER_ACTION); 852 fillIn.putExtra(Intent.EXTRA_INTENT, intent); 853 try { 854 mTarget.sendIntent(mContext, 0, fillIn, null, null); 855 } catch (SendIntentException ignored) { 856 } 857 } 858 859 @Override 860 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 861 Bundle extras) { 862 if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) { 863 boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING); 864 Notification notification = buildSuccessNotification(mContext, 865 mContext.getResources() 866 .getString(update ? R.string.package_updated_device_owner : 867 R.string.package_installed_device_owner), 868 basePackageName, 869 mUserId); 870 if (notification != null) { 871 NotificationManager notificationManager = (NotificationManager) 872 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 873 notificationManager.notify(basePackageName, 874 SystemMessage.NOTE_PACKAGE_STATE, 875 notification); 876 } 877 } 878 final Intent fillIn = new Intent(); 879 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName); 880 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); 881 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 882 PackageManager.installStatusToPublicStatus(returnCode)); 883 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, 884 PackageManager.installStatusToString(returnCode, msg)); 885 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); 886 if (extras != null) { 887 final String existing = extras.getString( 888 PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); 889 if (!TextUtils.isEmpty(existing)) { 890 fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); 891 } 892 } 893 try { 894 mTarget.sendIntent(mContext, 0, fillIn, null, null); 895 } catch (SendIntentException ignored) { 896 } 897 } 898 } 899 900 /** 901 * Build a notification for package installation / deletion by device owners that is shown if 902 * the operation succeeds. 903 */ 904 private static Notification buildSuccessNotification(Context context, String contentText, 905 String basePackageName, int userId) { 906 PackageInfo packageInfo = null; 907 try { 908 packageInfo = AppGlobals.getPackageManager().getPackageInfo( 909 basePackageName, PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId); 910 } catch (RemoteException ignored) { 911 } 912 if (packageInfo == null || packageInfo.applicationInfo == null) { 913 Slog.w(TAG, "Notification not built for package: " + basePackageName); 914 return null; 915 } 916 PackageManager pm = context.getPackageManager(); 917 Bitmap packageIcon = ImageUtils.buildScaledBitmap( 918 packageInfo.applicationInfo.loadIcon(pm), 919 context.getResources().getDimensionPixelSize( 920 android.R.dimen.notification_large_icon_width), 921 context.getResources().getDimensionPixelSize( 922 android.R.dimen.notification_large_icon_height)); 923 CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm); 924 return new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN) 925 .setSmallIcon(R.drawable.ic_check_circle_24px) 926 .setColor(context.getResources().getColor( 927 R.color.system_notification_accent_color)) 928 .setContentTitle(packageLabel) 929 .setContentText(contentText) 930 .setStyle(new Notification.BigTextStyle().bigText(contentText)) 931 .setLargeIcon(packageIcon) 932 .build(); 933 } 934 935 public static <E> ArraySet<E> newArraySet(E... elements) { 936 final ArraySet<E> set = new ArraySet<E>(); 937 if (elements != null) { 938 set.ensureCapacity(elements.length); 939 Collections.addAll(set, elements); 940 } 941 return set; 942 } 943 944 private static class Callbacks extends Handler { 945 private static final int MSG_SESSION_CREATED = 1; 946 private static final int MSG_SESSION_BADGING_CHANGED = 2; 947 private static final int MSG_SESSION_ACTIVE_CHANGED = 3; 948 private static final int MSG_SESSION_PROGRESS_CHANGED = 4; 949 private static final int MSG_SESSION_FINISHED = 5; 950 951 private final RemoteCallbackList<IPackageInstallerCallback> 952 mCallbacks = new RemoteCallbackList<>(); 953 954 public Callbacks(Looper looper) { 955 super(looper); 956 } 957 958 public void register(IPackageInstallerCallback callback, int userId) { 959 mCallbacks.register(callback, new UserHandle(userId)); 960 } 961 962 public void unregister(IPackageInstallerCallback callback) { 963 mCallbacks.unregister(callback); 964 } 965 966 @Override 967 public void handleMessage(Message msg) { 968 final int userId = msg.arg2; 969 final int n = mCallbacks.beginBroadcast(); 970 for (int i = 0; i < n; i++) { 971 final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); 972 final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); 973 // TODO: dispatch notifications for slave profiles 974 if (userId == user.getIdentifier()) { 975 try { 976 invokeCallback(callback, msg); 977 } catch (RemoteException ignored) { 978 } 979 } 980 } 981 mCallbacks.finishBroadcast(); 982 } 983 984 private void invokeCallback(IPackageInstallerCallback callback, Message msg) 985 throws RemoteException { 986 final int sessionId = msg.arg1; 987 switch (msg.what) { 988 case MSG_SESSION_CREATED: 989 callback.onSessionCreated(sessionId); 990 break; 991 case MSG_SESSION_BADGING_CHANGED: 992 callback.onSessionBadgingChanged(sessionId); 993 break; 994 case MSG_SESSION_ACTIVE_CHANGED: 995 callback.onSessionActiveChanged(sessionId, (boolean) msg.obj); 996 break; 997 case MSG_SESSION_PROGRESS_CHANGED: 998 callback.onSessionProgressChanged(sessionId, (float) msg.obj); 999 break; 1000 case MSG_SESSION_FINISHED: 1001 callback.onSessionFinished(sessionId, (boolean) msg.obj); 1002 break; 1003 } 1004 } 1005 1006 private void notifySessionCreated(int sessionId, int userId) { 1007 obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget(); 1008 } 1009 1010 private void notifySessionBadgingChanged(int sessionId, int userId) { 1011 obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget(); 1012 } 1013 1014 private void notifySessionActiveChanged(int sessionId, int userId, boolean active) { 1015 obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget(); 1016 } 1017 1018 private void notifySessionProgressChanged(int sessionId, int userId, float progress) { 1019 obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget(); 1020 } 1021 1022 public void notifySessionFinished(int sessionId, int userId, boolean success) { 1023 obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget(); 1024 } 1025 } 1026 1027 void dump(IndentingPrintWriter pw) { 1028 synchronized (mSessions) { 1029 pw.println("Active install sessions:"); 1030 pw.increaseIndent(); 1031 int N = mSessions.size(); 1032 for (int i = 0; i < N; i++) { 1033 final PackageInstallerSession session = mSessions.valueAt(i); 1034 session.dump(pw); 1035 pw.println(); 1036 } 1037 pw.println(); 1038 pw.decreaseIndent(); 1039 1040 pw.println("Historical install sessions:"); 1041 pw.increaseIndent(); 1042 N = mHistoricalSessions.size(); 1043 for (int i = 0; i < N; i++) { 1044 pw.print(mHistoricalSessions.get(i)); 1045 pw.println(); 1046 } 1047 pw.println(); 1048 pw.decreaseIndent(); 1049 1050 pw.println("Legacy install sessions:"); 1051 pw.increaseIndent(); 1052 pw.println(mLegacySessions.toString()); 1053 pw.decreaseIndent(); 1054 } 1055 } 1056 1057 class InternalCallback { 1058 public void onSessionBadgingChanged(PackageInstallerSession session) { 1059 mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); 1060 writeSessionsAsync(); 1061 } 1062 1063 public void onSessionActiveChanged(PackageInstallerSession session, boolean active) { 1064 mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active); 1065 } 1066 1067 public void onSessionProgressChanged(PackageInstallerSession session, float progress) { 1068 mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); 1069 } 1070 1071 public void onSessionFinished(final PackageInstallerSession session, boolean success) { 1072 mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); 1073 1074 mInstallHandler.post(new Runnable() { 1075 @Override 1076 public void run() { 1077 synchronized (mSessions) { 1078 mSessions.remove(session.sessionId); 1079 addHistoricalSessionLocked(session); 1080 1081 final File appIconFile = buildAppIconFile(session.sessionId); 1082 if (appIconFile.exists()) { 1083 appIconFile.delete(); 1084 } 1085 1086 writeSessionsLocked(); 1087 } 1088 } 1089 }); 1090 } 1091 1092 public void onSessionPrepared(PackageInstallerSession session) { 1093 // We prepared the destination to write into; we want to persist 1094 // this, but it's not critical enough to block for. 1095 writeSessionsAsync(); 1096 } 1097 1098 public void onSessionSealedBlocking(PackageInstallerSession session) { 1099 // It's very important that we block until we've recorded the 1100 // session as being sealed, since we never want to allow mutation 1101 // after sealing. 1102 synchronized (mSessions) { 1103 writeSessionsLocked(); 1104 } 1105 } 1106 } 1107} 1108