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