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