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