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