2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.pm;
19 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
20 import static org.xmlpull.v1.XmlPullParser.START_TAG;
22 import android.Manifest;
23 import android.app.ActivityManager;
24 import android.app.AppGlobals;
25 import android.app.AppOpsManager;
26 import android.app.Notification;
27 import android.app.NotificationManager;
28 import android.app.PackageDeleteObserver;
29 import android.app.PackageInstallObserver;
30 import android.app.admin.DevicePolicyManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentSender;
34 import android.content.IntentSender.SendIntentException;
35 import android.content.pm.IPackageInstaller;
36 import android.content.pm.IPackageInstallerCallback;
37 import android.content.pm.IPackageInstallerSession;
38 import android.content.pm.PackageInfo;
39 import android.content.pm.PackageInstaller;
40 import android.content.pm.PackageInstaller.SessionInfo;
41 import android.content.pm.PackageInstaller.SessionParams;
42 import android.content.pm.PackageManager;
43 import android.content.pm.ParceledListSlice;
44 import android.content.pm.VersionedPackage;
45 import android.graphics.Bitmap;
46 import android.net.Uri;
47 import android.os.Binder;
48 import android.os.Bundle;
49 import android.os.Environment;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.os.Looper;
53 import android.os.Message;
54 import android.os.Process;
55 import android.os.RemoteCallbackList;
56 import android.os.RemoteException;
57 import android.os.SELinux;
58 import android.os.UserHandle;
59 import android.os.UserManager;
60 import android.os.storage.StorageManager;
61 import android.system.ErrnoException;
62 import android.system.Os;
63 import android.text.TextUtils;
64 import android.text.format.DateUtils;
65 import android.util.ArraySet;
66 import android.util.AtomicFile;
67 import android.util.ExceptionUtils;
68 import android.util.Slog;
69 import android.util.SparseArray;
70 import android.util.SparseBooleanArray;
71 import android.util.SparseIntArray;
72 import android.util.Xml;
74 import com.android.internal.R;
75 import com.android.internal.annotations.GuardedBy;
76 import com.android.internal.content.PackageHelper;
77 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
78 import com.android.internal.notification.SystemNotificationChannels;
79 import com.android.internal.util.FastXmlSerializer;
80 import com.android.internal.util.ImageUtils;
81 import com.android.internal.util.IndentingPrintWriter;
82 import com.android.server.IoThread;
84 import libcore.io.IoUtils;
86 import org.xmlpull.v1.XmlPullParser;
87 import org.xmlpull.v1.XmlPullParserException;
88 import org.xmlpull.v1.XmlSerializer;
90 import java.io.CharArrayWriter;
92 import java.io.FileInputStream;
93 import java.io.FileNotFoundException;
94 import java.io.FileOutputStream;
95 import java.io.FilenameFilter;
96 import java.io.IOException;
97 import java.nio.charset.StandardCharsets;
98 import java.security.SecureRandom;
99 import java.util.ArrayList;
100 import java.util.Collections;
101 import java.util.List;
102 import java.util.Objects;
103 import java.util.Random;
105 public class PackageInstallerService extends IPackageInstaller.Stub {
106 private static final String TAG = "PackageInstaller";
107 private static final boolean LOGD = false;
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
113 /** XML constants used in {@link #mSessionsFile} */
114 private static final String TAG_SESSIONS = "sessions";
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;
123 private final Context mContext;
124 private final PackageManagerService mPm;
126 private AppOpsManager mAppOps;
128 private final HandlerThread mInstallThread;
129 private final Handler mInstallHandler;
131 private final Callbacks mCallbacks;
134 * File storing persisted {@link #mSessions} metadata.
136 private final AtomicFile mSessionsFile;
139 * Directory storing persisted {@link #mSessions} metadata which is too
140 * heavy to store directly in {@link #mSessionsFile}.
142 private final File mSessionsDir;
144 private final InternalCallback mInternalCallback = new InternalCallback();
147 * Used for generating session IDs. Since this is created at boot time,
148 * normal random might be predictable.
150 private final Random mRandom = new SecureRandom();
152 /** All sessions allocated */
153 @GuardedBy("mSessions")
154 private final SparseBooleanArray mAllocatedSessions = new SparseBooleanArray();
156 @GuardedBy("mSessions")
157 private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
159 /** Historical sessions kept around for debugging purposes */
160 @GuardedBy("mSessions")
161 private final List<String> mHistoricalSessions = new ArrayList<>();
163 @GuardedBy("mSessions")
164 private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
166 /** Sessions allocated to legacy users */
167 @GuardedBy("mSessions")
168 private final SparseBooleanArray mLegacySessions = new SparseBooleanArray();
170 private static final FilenameFilter sStageFilter = new FilenameFilter() {
172 public boolean accept(File dir, String name) {
173 return isStageName(name);
177 public PackageInstallerService(Context context, PackageManagerService pm) {
181 mInstallThread = new HandlerThread(TAG);
182 mInstallThread.start();
184 mInstallHandler = new Handler(mInstallThread.getLooper());
186 mCallbacks = new Callbacks(mInstallThread.getLooper());
188 mSessionsFile = new AtomicFile(
189 new File(Environment.getDataSystemDirectory(), "install_sessions.xml"));
190 mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
191 mSessionsDir.mkdirs();
194 public void systemReady() {
195 mAppOps = mContext.getSystemService(AppOpsManager.class);
197 synchronized (mSessions) {
198 readSessionsLocked();
200 reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/);
201 reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/);
203 final ArraySet<File> unclaimedIcons = newArraySet(
204 mSessionsDir.listFiles());
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));
212 // Clean up orphaned icons
213 for (File icon : unclaimedIcons) {
214 Slog.w(TAG, "Deleting orphan icon " + icon);
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));
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);
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);
240 public void onPrivateVolumeMounted(String volumeUuid) {
241 synchronized (mSessions) {
242 reconcileStagesLocked(volumeUuid, false /*isInstant*/);
246 public void onSecureContainersAvailable() {
247 synchronized (mSessions) {
248 final ArraySet<String> unclaimed = new ArraySet<>();
249 for (String cid : PackageHelper.getSecureContainerList()) {
250 if (isStageName(cid)) {
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;
260 if (unclaimed.remove(cid)) {
261 // Claimed by active session, mount it
262 PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
267 // Clean up orphaned staging containers
268 for (String cid : unclaimed) {
269 Slog.w(TAG, "Deleting orphan container " + cid);
270 PackageHelper.destroySdDir(cid);
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;
283 public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException {
284 synchronized (mSessions) {
286 final int sessionId = allocateSessionIdLocked();
287 mLegacySessions.put(sessionId, true);
288 final File stageDir = buildStageDir(volumeUuid, sessionId, isEphemeral);
289 prepareStageDir(stageDir);
291 } catch (IllegalStateException e) {
292 throw new IOException(e);
298 public String allocateExternalStageCidLegacy() {
299 synchronized (mSessions) {
300 final int sessionId = allocateSessionIdLocked();
301 mLegacySessions.put(sessionId, true);
302 return "smdl" + sessionId + ".tmp";
306 private void readSessionsLocked() {
307 if (LOGD) Slog.v(TAG, "readSessionsLocked()");
311 FileInputStream fis = null;
313 fis = mSessionsFile.openRead();
314 final XmlPullParser in = Xml.newPullParser();
315 in.setInput(fis, StandardCharsets.UTF_8.name());
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;
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);
331 final long age = System.currentTimeMillis() - session.createdMillis;
334 if (age >= MAX_AGE_MILLIS) {
335 Slog.w(TAG, "Abandoning old session first created at "
336 + session.createdMillis);
343 mSessions.put(session.sessionId, session);
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);
350 mAllocatedSessions.put(session.sessionId, true);
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);
359 IoUtils.closeQuietly(fis);
363 private void addHistoricalSessionLocked(PackageInstallerSession session) {
364 CharArrayWriter writer = new CharArrayWriter();
365 IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
367 mHistoricalSessions.add(writer.toString());
369 int installerUid = session.getInstallerUid();
370 // Increment the number of sessions by this installerUid.
371 mHistoricalSessionsByInstaller.put(installerUid,
372 mHistoricalSessionsByInstaller.get(installerUid) + 1);
375 private void writeSessionsLocked() {
376 if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
378 FileOutputStream fos = null;
380 fos = mSessionsFile.startWrite();
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);
391 out.endTag(null, TAG_SESSIONS);
394 mSessionsFile.finishWrite(fos);
395 } catch (IOException e) {
397 mSessionsFile.failWrite(fos);
402 private File buildAppIconFile(int sessionId) {
403 return new File(mSessionsDir, "app_icon." + sessionId + ".png");
406 private void writeSessionsAsync() {
407 IoThread.getHandler().post(new Runnable() {
410 synchronized (mSessions) {
411 writeSessionsLocked();
418 public int createSession(SessionParams params, String installerPackageName, int userId) {
420 return createSessionInternal(params, installerPackageName, userId);
421 } catch (IOException e) {
422 throw ExceptionUtils.wrap(e);
426 private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
428 final int callingUid = Binder.getCallingUid();
429 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
431 if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
432 throw new SecurityException("User restriction prevents installing");
435 if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
436 params.installFlags |= PackageManager.INSTALL_FROM_ADB;
439 mAppOps.checkPackage(callingUid, installerPackageName);
441 params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
442 params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
443 params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
444 params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
445 if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
446 && !mPm.isCallerVerifier(callingUid)) {
447 params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
451 // Only system components can circumvent runtime permissions when installing.
452 if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
453 && mContext.checkCallingOrSelfPermission(Manifest.permission
454 .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
455 throw new SecurityException("You need the "
456 + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
457 + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
460 if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
461 || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
462 throw new IllegalArgumentException(
463 "New installs into ASEC containers no longer supported");
466 // Defensively resize giant app icons
467 if (params.appIcon != null) {
468 final ActivityManager am = (ActivityManager) mContext.getSystemService(
469 Context.ACTIVITY_SERVICE);
470 final int iconSize = am.getLauncherLargeIconSize();
471 if ((params.appIcon.getWidth() > iconSize * 2)
472 || (params.appIcon.getHeight() > iconSize * 2)) {
473 params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
478 switch (params.mode) {
479 case SessionParams.MODE_FULL_INSTALL:
480 case SessionParams.MODE_INHERIT_EXISTING:
483 throw new IllegalArgumentException("Invalid install mode: " + params.mode);
486 // If caller requested explicit location, sanity check it, otherwise
487 // resolve the best internal or adopted location.
488 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
489 if (!PackageHelper.fitsOnInternal(mContext, params)) {
490 throw new IOException("No suitable internal storage available");
493 } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
494 if (!PackageHelper.fitsOnExternal(mContext, params)) {
495 throw new IOException("No suitable external storage available");
498 } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
499 // For now, installs to adopted media are treated as internal from
500 // an install flag point-of-view.
501 params.setInstallFlagsInternal();
504 // For now, installs to adopted media are treated as internal from
505 // an install flag point-of-view.
506 params.setInstallFlagsInternal();
508 // Resolve best location for install, based on combination of
509 // requested install flags, delta size, and manifest settings.
510 final long ident = Binder.clearCallingIdentity();
512 params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
514 Binder.restoreCallingIdentity(ident);
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);
527 final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
528 if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
529 throw new IllegalStateException(
530 "Too many historical sessions for UID " + callingUid);
533 sessionId = allocateSessionIdLocked();
536 final long createdMillis = System.currentTimeMillis();
537 // We're staging to exactly one location
538 File stageDir = null;
539 String stageCid = null;
540 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
541 final boolean isInstant =
542 (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
543 stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
545 stageCid = buildExternalStageCid(sessionId);
548 session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
549 mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
550 params, createdMillis, stageDir, stageCid, false, false);
552 synchronized (mSessions) {
553 mSessions.put(sessionId, session);
556 mCallbacks.notifySessionCreated(session.sessionId, session.userId);
557 writeSessionsAsync();
562 public void updateSessionAppIcon(int sessionId, Bitmap appIcon) {
563 synchronized (mSessions) {
564 final PackageInstallerSession session = mSessions.get(sessionId);
565 if (session == null || !isCallingUidOwner(session)) {
566 throw new SecurityException("Caller has no access to session " + sessionId);
569 // Defensively resize giant app icons
570 if (appIcon != null) {
571 final ActivityManager am = (ActivityManager) mContext.getSystemService(
572 Context.ACTIVITY_SERVICE);
573 final int iconSize = am.getLauncherLargeIconSize();
574 if ((appIcon.getWidth() > iconSize * 2)
575 || (appIcon.getHeight() > iconSize * 2)) {
576 appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true);
580 session.params.appIcon = appIcon;
581 session.params.appIconLastModified = -1;
583 mInternalCallback.onSessionBadgingChanged(session);
588 public void updateSessionAppLabel(int sessionId, String appLabel) {
589 synchronized (mSessions) {
590 final PackageInstallerSession session = mSessions.get(sessionId);
591 if (session == null || !isCallingUidOwner(session)) {
592 throw new SecurityException("Caller has no access to session " + sessionId);
594 session.params.appLabel = appLabel;
595 mInternalCallback.onSessionBadgingChanged(session);
600 public void abandonSession(int sessionId) {
601 synchronized (mSessions) {
602 final PackageInstallerSession session = mSessions.get(sessionId);
603 if (session == null || !isCallingUidOwner(session)) {
604 throw new SecurityException("Caller has no access to session " + sessionId);
611 public IPackageInstallerSession openSession(int sessionId) {
613 return openSessionInternal(sessionId);
614 } catch (IOException e) {
615 throw ExceptionUtils.wrap(e);
619 private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
620 synchronized (mSessions) {
621 final PackageInstallerSession session = mSessions.get(sessionId);
622 if (session == null || !isCallingUidOwner(session)) {
623 throw new SecurityException("Caller has no access to session " + sessionId);
630 private int allocateSessionIdLocked() {
634 sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
635 if (!mAllocatedSessions.get(sessionId, false)) {
636 mAllocatedSessions.put(sessionId, true);
641 throw new IllegalStateException("Failed to allocate session ID");
644 private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
645 return Environment.getDataAppDirectory(volumeUuid);
648 private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
649 final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
650 return new File(stagingDir, "vmdl" + sessionId + ".tmp");
653 static void prepareStageDir(File stageDir) throws IOException {
654 if (stageDir.exists()) {
655 throw new IOException("Session dir already exists: " + stageDir);
659 Os.mkdir(stageDir.getAbsolutePath(), 0755);
660 Os.chmod(stageDir.getAbsolutePath(), 0755);
661 } catch (ErrnoException e) {
662 // This purposefully throws if directory already exists
663 throw new IOException("Failed to prepare session dir: " + stageDir, e);
666 if (!SELinux.restorecon(stageDir)) {
667 throw new IOException("Failed to restorecon session dir: " + stageDir);
671 private String buildExternalStageCid(int sessionId) {
672 return "smdl" + sessionId + ".tmp";
675 static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException {
676 if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(),
677 Process.SYSTEM_UID, true) == null) {
678 throw new IOException("Failed to create session cid: " + stageCid);
683 public SessionInfo getSessionInfo(int sessionId) {
684 synchronized (mSessions) {
685 final PackageInstallerSession session = mSessions.get(sessionId);
686 return session != null ? session.generateInfo() : null;
691 public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
692 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions");
694 final List<SessionInfo> result = new ArrayList<>();
695 synchronized (mSessions) {
696 for (int i = 0; i < mSessions.size(); i++) {
697 final PackageInstallerSession session = mSessions.valueAt(i);
698 if (session.userId == userId) {
699 result.add(session.generateInfo(false));
703 return new ParceledListSlice<>(result);
707 public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
708 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions");
709 mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
711 final List<SessionInfo> result = new ArrayList<>();
712 synchronized (mSessions) {
713 for (int i = 0; i < mSessions.size(); i++) {
714 final PackageInstallerSession session = mSessions.valueAt(i);
716 SessionInfo info = session.generateInfo(false);
717 if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
718 && session.userId == userId) {
723 return new ParceledListSlice<>(result);
727 public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
728 IntentSender statusReceiver, int userId) throws RemoteException {
729 final int callingUid = Binder.getCallingUid();
730 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
731 if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
732 mAppOps.checkPackage(callingUid, callerPackageName);
735 // Check whether the caller is device owner, in which case we do it silently.
736 DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
737 Context.DEVICE_POLICY_SERVICE);
738 boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
741 final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
742 statusReceiver, versionedPackage.getPackageName(), isDeviceOwner, userId);
743 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
744 == PackageManager.PERMISSION_GRANTED) {
745 // Sweet, call straight through!
746 mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
747 } else if (isDeviceOwner) {
748 // Allow the DeviceOwner to silently delete packages
749 // Need to clear the calling identity to get DELETE_PACKAGES permission
750 long ident = Binder.clearCallingIdentity();
752 mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
754 Binder.restoreCallingIdentity(ident);
757 // Take a short detour to confirm with user
758 final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
759 intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
760 intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
761 adapter.onUserActionRequired(intent);
766 public void setPermissionsResult(int sessionId, boolean accepted) {
767 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
769 synchronized (mSessions) {
770 PackageInstallerSession session = mSessions.get(sessionId);
771 if (session != null) {
772 session.setPermissionsResult(accepted);
778 public void registerCallback(IPackageInstallerCallback callback, int userId) {
779 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback");
780 mCallbacks.register(callback, userId);
784 public void unregisterCallback(IPackageInstallerCallback callback) {
785 mCallbacks.unregister(callback);
788 private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
791 final int size = sessions.size();
792 for (int i = 0; i < size; i++) {
793 final PackageInstallerSession session = sessions.valueAt(i);
794 if (session.getInstallerUid() == installerUid) {
801 private boolean isCallingUidOwner(PackageInstallerSession session) {
802 final int callingUid = Binder.getCallingUid();
803 if (callingUid == Process.ROOT_UID) {
806 return (session != null) && (callingUid == session.getInstallerUid());
810 static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
811 private final Context mContext;
812 private final IntentSender mTarget;
813 private final String mPackageName;
814 private final Notification mNotification;
816 public PackageDeleteObserverAdapter(Context context, IntentSender target,
817 String packageName, boolean showNotification, int userId) {
820 mPackageName = packageName;
821 if (showNotification) {
822 mNotification = buildSuccessNotification(mContext,
823 mContext.getResources().getString(R.string.package_deleted_device_owner),
827 mNotification = null;
832 public void onUserActionRequired(Intent intent) {
833 final Intent fillIn = new Intent();
834 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
835 fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
836 PackageInstaller.STATUS_PENDING_USER_ACTION);
837 fillIn.putExtra(Intent.EXTRA_INTENT, intent);
839 mTarget.sendIntent(mContext, 0, fillIn, null, null);
840 } catch (SendIntentException ignored) {
845 public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
846 if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) {
847 NotificationManager notificationManager = (NotificationManager)
848 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
849 notificationManager.notify(basePackageName,
850 SystemMessage.NOTE_PACKAGE_STATE,
853 final Intent fillIn = new Intent();
854 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
855 fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
856 PackageManager.deleteStatusToPublicStatus(returnCode));
857 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
858 PackageManager.deleteStatusToString(returnCode, msg));
859 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
861 mTarget.sendIntent(mContext, 0, fillIn, null, null);
862 } catch (SendIntentException ignored) {
867 static class PackageInstallObserverAdapter extends PackageInstallObserver {
868 private final Context mContext;
869 private final IntentSender mTarget;
870 private final int mSessionId;
871 private final boolean mShowNotification;
872 private final int mUserId;
874 public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
875 boolean showNotification, int userId) {
878 mSessionId = sessionId;
879 mShowNotification = showNotification;
884 public void onUserActionRequired(Intent intent) {
885 final Intent fillIn = new Intent();
886 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
887 fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
888 PackageInstaller.STATUS_PENDING_USER_ACTION);
889 fillIn.putExtra(Intent.EXTRA_INTENT, intent);
891 mTarget.sendIntent(mContext, 0, fillIn, null, null);
892 } catch (SendIntentException ignored) {
897 public void onPackageInstalled(String basePackageName, int returnCode, String msg,
899 if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
900 boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
901 Notification notification = buildSuccessNotification(mContext,
902 mContext.getResources()
903 .getString(update ? R.string.package_updated_device_owner :
904 R.string.package_installed_device_owner),
907 if (notification != null) {
908 NotificationManager notificationManager = (NotificationManager)
909 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
910 notificationManager.notify(basePackageName,
911 SystemMessage.NOTE_PACKAGE_STATE,
915 final Intent fillIn = new Intent();
916 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);
917 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
918 fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
919 PackageManager.installStatusToPublicStatus(returnCode));
920 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
921 PackageManager.installStatusToString(returnCode, msg));
922 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
923 if (extras != null) {
924 final String existing = extras.getString(
925 PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
926 if (!TextUtils.isEmpty(existing)) {
927 fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
931 mTarget.sendIntent(mContext, 0, fillIn, null, null);
932 } catch (SendIntentException ignored) {
938 * Build a notification for package installation / deletion by device owners that is shown if
939 * the operation succeeds.
941 private static Notification buildSuccessNotification(Context context, String contentText,
942 String basePackageName, int userId) {
943 PackageInfo packageInfo = null;
945 packageInfo = AppGlobals.getPackageManager().getPackageInfo(
946 basePackageName, PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
947 } catch (RemoteException ignored) {
949 if (packageInfo == null || packageInfo.applicationInfo == null) {
950 Slog.w(TAG, "Notification not built for package: " + basePackageName);
953 PackageManager pm = context.getPackageManager();
954 Bitmap packageIcon = ImageUtils.buildScaledBitmap(
955 packageInfo.applicationInfo.loadIcon(pm),
956 context.getResources().getDimensionPixelSize(
957 android.R.dimen.notification_large_icon_width),
958 context.getResources().getDimensionPixelSize(
959 android.R.dimen.notification_large_icon_height));
960 CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm);
961 return new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
962 .setSmallIcon(R.drawable.ic_check_circle_24px)
963 .setColor(context.getResources().getColor(
964 R.color.system_notification_accent_color))
965 .setContentTitle(packageLabel)
966 .setContentText(contentText)
967 .setStyle(new Notification.BigTextStyle().bigText(contentText))
968 .setLargeIcon(packageIcon)
972 public static <E> ArraySet<E> newArraySet(E... elements) {
973 final ArraySet<E> set = new ArraySet<E>();
974 if (elements != null) {
975 set.ensureCapacity(elements.length);
976 Collections.addAll(set, elements);
981 private static class Callbacks extends Handler {
982 private static final int MSG_SESSION_CREATED = 1;
983 private static final int MSG_SESSION_BADGING_CHANGED = 2;
984 private static final int MSG_SESSION_ACTIVE_CHANGED = 3;
985 private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
986 private static final int MSG_SESSION_FINISHED = 5;
988 private final RemoteCallbackList<IPackageInstallerCallback>
989 mCallbacks = new RemoteCallbackList<>();
991 public Callbacks(Looper looper) {
995 public void register(IPackageInstallerCallback callback, int userId) {
996 mCallbacks.register(callback, new UserHandle(userId));
999 public void unregister(IPackageInstallerCallback callback) {
1000 mCallbacks.unregister(callback);
1004 public void handleMessage(Message msg) {
1005 final int userId = msg.arg2;
1006 final int n = mCallbacks.beginBroadcast();
1007 for (int i = 0; i < n; i++) {
1008 final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
1009 final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
1010 // TODO: dispatch notifications for slave profiles
1011 if (userId == user.getIdentifier()) {
1013 invokeCallback(callback, msg);
1014 } catch (RemoteException ignored) {
1018 mCallbacks.finishBroadcast();
1021 private void invokeCallback(IPackageInstallerCallback callback, Message msg)
1022 throws RemoteException {
1023 final int sessionId = msg.arg1;
1025 case MSG_SESSION_CREATED:
1026 callback.onSessionCreated(sessionId);
1028 case MSG_SESSION_BADGING_CHANGED:
1029 callback.onSessionBadgingChanged(sessionId);
1031 case MSG_SESSION_ACTIVE_CHANGED:
1032 callback.onSessionActiveChanged(sessionId, (boolean) msg.obj);
1034 case MSG_SESSION_PROGRESS_CHANGED:
1035 callback.onSessionProgressChanged(sessionId, (float) msg.obj);
1037 case MSG_SESSION_FINISHED:
1038 callback.onSessionFinished(sessionId, (boolean) msg.obj);
1043 private void notifySessionCreated(int sessionId, int userId) {
1044 obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
1047 private void notifySessionBadgingChanged(int sessionId, int userId) {
1048 obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget();
1051 private void notifySessionActiveChanged(int sessionId, int userId, boolean active) {
1052 obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget();
1055 private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
1056 obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
1059 public void notifySessionFinished(int sessionId, int userId, boolean success) {
1060 obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
1064 void dump(IndentingPrintWriter pw) {
1065 synchronized (mSessions) {
1066 pw.println("Active install sessions:");
1067 pw.increaseIndent();
1068 int N = mSessions.size();
1069 for (int i = 0; i < N; i++) {
1070 final PackageInstallerSession session = mSessions.valueAt(i);
1075 pw.decreaseIndent();
1077 pw.println("Historical install sessions:");
1078 pw.increaseIndent();
1079 N = mHistoricalSessions.size();
1080 for (int i = 0; i < N; i++) {
1081 pw.print(mHistoricalSessions.get(i));
1085 pw.decreaseIndent();
1087 pw.println("Legacy install sessions:");
1088 pw.increaseIndent();
1089 pw.println(mLegacySessions.toString());
1090 pw.decreaseIndent();
1094 class InternalCallback {
1095 public void onSessionBadgingChanged(PackageInstallerSession session) {
1096 mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
1097 writeSessionsAsync();
1100 public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
1101 mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active);
1104 public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
1105 mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
1108 public void onSessionFinished(final PackageInstallerSession session, boolean success) {
1109 mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
1111 mInstallHandler.post(new Runnable() {
1114 synchronized (mSessions) {
1115 mSessions.remove(session.sessionId);
1116 addHistoricalSessionLocked(session);
1118 final File appIconFile = buildAppIconFile(session.sessionId);
1119 if (appIconFile.exists()) {
1120 appIconFile.delete();
1123 writeSessionsLocked();
1129 public void onSessionPrepared(PackageInstallerSession session) {
1130 // We prepared the destination to write into; we want to persist
1131 // this, but it's not critical enough to block for.
1132 writeSessionsAsync();
1135 public void onSessionSealedBlocking(PackageInstallerSession session) {
1136 // It's very important that we block until we've recorded the
1137 // session as being sealed, since we never want to allow mutation
1139 synchronized (mSessions) {
1140 writeSessionsLocked();