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