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