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