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