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