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