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