PackageInstallerService.java revision 7328a1b39b3dae1c0cd390c0a3695c6a46b8e9d8
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 android.content.pm.PackageManager.INSTALL_ALL_USERS;
20import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
21import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
22import static com.android.internal.util.XmlUtils.readBitmapAttribute;
23import static com.android.internal.util.XmlUtils.readBooleanAttribute;
24import static com.android.internal.util.XmlUtils.readIntAttribute;
25import static com.android.internal.util.XmlUtils.readLongAttribute;
26import static com.android.internal.util.XmlUtils.readStringAttribute;
27import static com.android.internal.util.XmlUtils.readUriAttribute;
28import static com.android.internal.util.XmlUtils.writeBitmapAttribute;
29import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
30import static com.android.internal.util.XmlUtils.writeIntAttribute;
31import static com.android.internal.util.XmlUtils.writeLongAttribute;
32import static com.android.internal.util.XmlUtils.writeStringAttribute;
33import static com.android.internal.util.XmlUtils.writeUriAttribute;
34import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
35import static org.xmlpull.v1.XmlPullParser.START_TAG;
36
37import android.app.ActivityManager;
38import android.app.AppOpsManager;
39import android.content.Context;
40import android.content.pm.IPackageDeleteObserver2;
41import android.content.pm.IPackageInstaller;
42import android.content.pm.IPackageInstallerCallback;
43import android.content.pm.IPackageInstallerSession;
44import android.content.pm.InstallSessionInfo;
45import android.content.pm.InstallSessionParams;
46import android.content.pm.PackageManager;
47import android.graphics.Bitmap;
48import android.os.Binder;
49import android.os.Environment;
50import android.os.FileUtils;
51import android.os.Handler;
52import android.os.HandlerThread;
53import android.os.Looper;
54import android.os.Message;
55import android.os.Process;
56import android.os.RemoteCallbackList;
57import android.os.RemoteException;
58import android.os.SELinux;
59import android.os.UserHandle;
60import android.os.UserManager;
61import android.system.ErrnoException;
62import android.system.Os;
63import android.text.format.DateUtils;
64import android.util.ArraySet;
65import android.util.AtomicFile;
66import android.util.ExceptionUtils;
67import android.util.Log;
68import android.util.Slog;
69import android.util.SparseArray;
70import android.util.Xml;
71
72import com.android.internal.annotations.GuardedBy;
73import com.android.internal.util.FastXmlSerializer;
74import com.android.internal.util.IndentingPrintWriter;
75import com.android.server.IoThread;
76import com.android.server.pm.PackageInstallerSession.Snapshot;
77import com.google.android.collect.Sets;
78
79import libcore.io.IoUtils;
80
81import org.xmlpull.v1.XmlPullParser;
82import org.xmlpull.v1.XmlPullParserException;
83import org.xmlpull.v1.XmlSerializer;
84
85import java.io.File;
86import java.io.FileInputStream;
87import java.io.FileNotFoundException;
88import java.io.FileOutputStream;
89import java.io.FilenameFilter;
90import java.io.IOException;
91import java.security.SecureRandom;
92import java.util.ArrayList;
93import java.util.List;
94import java.util.Objects;
95import java.util.Random;
96
97public class PackageInstallerService extends IPackageInstaller.Stub {
98    private static final String TAG = "PackageInstaller";
99    private static final boolean LOGD = true;
100
101    // TODO: remove outstanding sessions when installer package goes away
102    // TODO: notify listeners in other users when package has been installed there
103
104    /** XML constants used in {@link #mSessionsFile} */
105    private static final String TAG_SESSIONS = "sessions";
106    private static final String TAG_SESSION = "session";
107    private static final String ATTR_SESSION_ID = "sessionId";
108    private static final String ATTR_USER_ID = "userId";
109    private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
110    private static final String ATTR_CREATED_MILLIS = "createdMillis";
111    private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
112    private static final String ATTR_SEALED = "sealed";
113    private static final String ATTR_MODE = "mode";
114    private static final String ATTR_INSTALL_FLAGS = "installFlags";
115    private static final String ATTR_INSTALL_LOCATION = "installLocation";
116    private static final String ATTR_SIZE_BYTES = "sizeBytes";
117    private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
118    private static final String ATTR_APP_ICON = "appIcon";
119    private static final String ATTR_APP_LABEL = "appLabel";
120    private static final String ATTR_ORIGINATING_URI = "originatingUri";
121    private static final String ATTR_REFERRER_URI = "referrerUri";
122    private static final String ATTR_ABI_OVERRIDE = "abiOverride";
123
124    /** Automatically destroy sessions older than this */
125    private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
126    /** Upper bound on number of active sessions for a UID */
127    private static final long MAX_ACTIVE_SESSIONS = 1024;
128    /** Upper bound on number of historical sessions for a UID */
129    private static final long MAX_HISTORICAL_SESSIONS = 1048576;
130
131    private final Context mContext;
132    private final PackageManagerService mPm;
133    private final AppOpsManager mAppOps;
134
135    private final File mStagingDir;
136    private final HandlerThread mInstallThread;
137
138    private final Callbacks mCallbacks;
139
140    /**
141     * File storing persisted {@link #mSessions}.
142     */
143    private final AtomicFile mSessionsFile;
144
145    private final InternalCallback mInternalCallback = new InternalCallback();
146
147    /**
148     * Used for generating session IDs. Since this is created at boot time,
149     * normal random might be predictable.
150     */
151    private final Random mRandom = new SecureRandom();
152
153    @GuardedBy("mSessions")
154    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
155
156    /** Historical sessions kept around for debugging purposes */
157    @GuardedBy("mSessions")
158    private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
159
160    private static final FilenameFilter sStageFilter = new FilenameFilter() {
161        @Override
162        public boolean accept(File dir, String name) {
163            return name.startsWith("vmdl") && name.endsWith(".tmp");
164        }
165    };
166
167    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
168        mContext = context;
169        mPm = pm;
170        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
171
172        mStagingDir = stagingDir;
173
174        mInstallThread = new HandlerThread(TAG);
175        mInstallThread.start();
176
177        mCallbacks = new Callbacks(mInstallThread.getLooper());
178
179        mSessionsFile = new AtomicFile(
180                new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
181
182        synchronized (mSessions) {
183            readSessionsLocked();
184
185            // Clean up orphaned staging directories
186            final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
187            for (int i = 0; i < mSessions.size(); i++) {
188                final PackageInstallerSession session = mSessions.valueAt(i);
189                stages.remove(session.sessionStageDir);
190            }
191            for (File stage : stages) {
192                Slog.w(TAG, "Deleting orphan stage " + stage);
193                if (stage.isDirectory()) {
194                    FileUtils.deleteContents(stage);
195                }
196                stage.delete();
197            }
198        }
199    }
200
201    public static boolean isStageFile(File file) {
202        return sStageFilter.accept(null, file.getName());
203    }
204
205    @Deprecated
206    public File allocateSessionDir() throws IOException {
207        synchronized (mSessions) {
208            try {
209                final int sessionId = allocateSessionIdLocked();
210                return prepareSessionStageDir(sessionId);
211            } catch (IllegalStateException e) {
212                throw new IOException(e);
213            }
214        }
215    }
216
217    private void readSessionsLocked() {
218        if (LOGD) Slog.v(TAG, "readSessionsLocked()");
219
220        mSessions.clear();
221
222        FileInputStream fis = null;
223        try {
224            fis = mSessionsFile.openRead();
225            final XmlPullParser in = Xml.newPullParser();
226            in.setInput(fis, null);
227
228            int type;
229            while ((type = in.next()) != END_DOCUMENT) {
230                if (type == START_TAG) {
231                    final String tag = in.getName();
232                    if (TAG_SESSION.equals(tag)) {
233                        final PackageInstallerSession session = readSessionLocked(in);
234                        final long age = System.currentTimeMillis() - session.createdMillis;
235
236                        final boolean valid;
237                        if (age >= MAX_AGE_MILLIS) {
238                            Slog.w(TAG, "Abandoning old session first created at "
239                                    + session.createdMillis);
240                            valid = false;
241                        } else if (!session.sessionStageDir.exists()) {
242                            Slog.w(TAG, "Abandoning session with missing stage "
243                                    + session.sessionStageDir);
244                            valid = false;
245                        } else {
246                            valid = true;
247                        }
248
249                        if (valid) {
250                            mSessions.put(session.sessionId, session);
251                        } else {
252                            // Since this is early during boot we don't send
253                            // any observer events about the session, but we
254                            // keep details around for dumpsys.
255                            mHistoricalSessions.put(session.sessionId, session);
256                        }
257                    }
258                }
259            }
260        } catch (FileNotFoundException e) {
261            // Missing sessions are okay, probably first boot
262        } catch (IOException e) {
263            Log.wtf(TAG, "Failed reading install sessions", e);
264        } catch (XmlPullParserException e) {
265            Log.wtf(TAG, "Failed reading install sessions", e);
266        } finally {
267            IoUtils.closeQuietly(fis);
268        }
269    }
270
271    private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException {
272        final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
273        final int userId = readIntAttribute(in, ATTR_USER_ID);
274        final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
275        final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
276        final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR));
277        final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
278
279        final InstallSessionParams params = new InstallSessionParams(
280                InstallSessionParams.MODE_INVALID);
281        params.mode = readIntAttribute(in, ATTR_MODE);
282        params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
283        params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
284        params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
285        params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
286        params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
287        params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
288        params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
289        params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
290        params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
291
292        return new PackageInstallerSession(mInternalCallback, mPm, mInstallThread.getLooper(),
293                sessionId, userId, installerPackageName, params, createdMillis, sessionStageDir,
294                sealed);
295    }
296
297    private void writeSessionsLocked() {
298        if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
299
300        FileOutputStream fos = null;
301        try {
302            fos = mSessionsFile.startWrite();
303
304            XmlSerializer out = new FastXmlSerializer();
305            out.setOutput(fos, "utf-8");
306            out.startDocument(null, true);
307            out.startTag(null, TAG_SESSIONS);
308            final int size = mSessions.size();
309            for (int i = 0; i < size; i++) {
310                final PackageInstallerSession session = mSessions.valueAt(i);
311                writeSessionLocked(out, session);
312            }
313            out.endTag(null, TAG_SESSIONS);
314            out.endDocument();
315
316            mSessionsFile.finishWrite(fos);
317        } catch (IOException e) {
318            if (fos != null) {
319                mSessionsFile.failWrite(fos);
320            }
321        }
322    }
323
324    private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
325            throws IOException {
326        final InstallSessionParams params = session.params;
327        final Snapshot snapshot = session.snapshot();
328
329        out.startTag(null, TAG_SESSION);
330
331        writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId);
332        writeIntAttribute(out, ATTR_USER_ID, session.userId);
333        writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
334                session.installerPackageName);
335        writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis);
336        writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
337                session.sessionStageDir.getAbsolutePath());
338        writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed);
339
340        writeIntAttribute(out, ATTR_MODE, params.mode);
341        writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
342        writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
343        writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
344        writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
345        writeBitmapAttribute(out, ATTR_APP_ICON, params.appIcon);
346        writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
347        writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
348        writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
349        writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
350
351        out.endTag(null, TAG_SESSION);
352    }
353
354    private void writeSessionsAsync() {
355        IoThread.getHandler().post(new Runnable() {
356            @Override
357            public void run() {
358                synchronized (mSessions) {
359                    writeSessionsLocked();
360                }
361            }
362        });
363    }
364
365    @Override
366    public int createSession(InstallSessionParams params, String installerPackageName, int userId) {
367        final int callingUid = Binder.getCallingUid();
368        mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
369
370        if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
371                UserManager.DISALLOW_INSTALL_APPS)) {
372            throw new SecurityException("User restriction prevents installing");
373        }
374
375        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
376            installerPackageName = "com.android.shell";
377
378            params.installFlags |= INSTALL_FROM_ADB;
379
380        } else {
381            mAppOps.checkPackage(callingUid, installerPackageName);
382
383            params.installFlags &= ~INSTALL_FROM_ADB;
384            params.installFlags &= ~INSTALL_ALL_USERS;
385            params.installFlags |= INSTALL_REPLACE_EXISTING;
386        }
387
388        switch (params.mode) {
389            case InstallSessionParams.MODE_FULL_INSTALL:
390            case InstallSessionParams.MODE_INHERIT_EXISTING:
391                break;
392            default:
393                throw new IllegalArgumentException("Params must have valid mode set");
394        }
395
396        // Defensively resize giant app icons
397        if (params.appIcon != null) {
398            final ActivityManager am = (ActivityManager) mContext.getSystemService(
399                    Context.ACTIVITY_SERVICE);
400            final int iconSize = am.getLauncherLargeIconSize();
401            if ((params.appIcon.getWidth() > iconSize * 2)
402                    || (params.appIcon.getHeight() > iconSize * 2)) {
403                params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
404                        true);
405            }
406        }
407
408        // Sanity check that install could fit
409        if (params.sizeBytes > 0) {
410            try {
411                mPm.freeStorage(params.sizeBytes);
412            } catch (IOException e) {
413                throw ExceptionUtils.wrap(e);
414            }
415        }
416
417        final int sessionId;
418        final PackageInstallerSession session;
419        synchronized (mSessions) {
420            // Sanity check that installer isn't going crazy
421            final int activeCount = getSessionCount(mSessions, callingUid);
422            if (activeCount >= MAX_ACTIVE_SESSIONS) {
423                throw new IllegalStateException(
424                        "Too many active sessions for UID " + callingUid);
425            }
426            final int historicalCount = getSessionCount(mHistoricalSessions, callingUid);
427            if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
428                throw new IllegalStateException(
429                        "Too many historical sessions for UID " + callingUid);
430            }
431
432            sessionId = allocateSessionIdLocked();
433
434            final long createdMillis = System.currentTimeMillis();
435            final File sessionStageDir = prepareSessionStageDir(sessionId);
436
437            session = new PackageInstallerSession(mInternalCallback, mPm,
438                    mInstallThread.getLooper(), sessionId, userId, installerPackageName, params,
439                    createdMillis, sessionStageDir, false);
440            mSessions.put(sessionId, session);
441        }
442
443        mCallbacks.notifySessionCreated(session.sessionId, session.userId);
444        writeSessionsAsync();
445        return sessionId;
446    }
447
448    @Override
449    public IPackageInstallerSession openSession(int sessionId) {
450        synchronized (mSessions) {
451            final PackageInstallerSession session = mSessions.get(sessionId);
452            if (session == null) {
453                throw new IllegalStateException("Missing session " + sessionId);
454            }
455            if (!isCallingUidOwner(session)) {
456                throw new SecurityException("Caller has no access to session " + sessionId);
457            }
458            if (session.openCount.getAndIncrement() == 0) {
459                mCallbacks.notifySessionOpened(sessionId, session.userId);
460            }
461            return session;
462        }
463    }
464
465    private int allocateSessionIdLocked() {
466        int n = 0;
467        int sessionId;
468        do {
469            sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
470            if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null) {
471                return sessionId;
472            }
473        } while (n++ < 32);
474
475        throw new IllegalStateException("Failed to allocate session ID");
476    }
477
478    private File prepareSessionStageDir(int sessionId) {
479        final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
480
481        if (file.exists()) {
482            throw new IllegalStateException("Session dir already exists: " + file);
483        }
484
485        try {
486            Os.mkdir(file.getAbsolutePath(), 0755);
487            Os.chmod(file.getAbsolutePath(), 0755);
488        } catch (ErrnoException e) {
489            // This purposefully throws if directory already exists
490            throw new IllegalStateException("Failed to prepare session dir", e);
491        }
492
493        if (!SELinux.restorecon(file)) {
494            throw new IllegalStateException("Failed to restorecon session dir");
495        }
496
497        return file;
498    }
499
500    @Override
501    public InstallSessionInfo getSessionInfo(int sessionId) {
502        synchronized (mSessions) {
503            final PackageInstallerSession session = mSessions.get(sessionId);
504            if (!isCallingUidOwner(session)) {
505                enforceCallerCanReadSessions();
506            }
507            return session != null ? session.generateInfo() : null;
508        }
509    }
510
511    @Override
512    public List<InstallSessionInfo> getAllSessions(int userId) {
513        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions");
514        enforceCallerCanReadSessions();
515
516        final List<InstallSessionInfo> result = new ArrayList<>();
517        synchronized (mSessions) {
518            for (int i = 0; i < mSessions.size(); i++) {
519                final PackageInstallerSession session = mSessions.valueAt(i);
520                if (session.userId == userId) {
521                    result.add(session.generateInfo());
522                }
523            }
524        }
525        return result;
526    }
527
528    @Override
529    public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) {
530        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions");
531        mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
532
533        final List<InstallSessionInfo> result = new ArrayList<>();
534        synchronized (mSessions) {
535            for (int i = 0; i < mSessions.size(); i++) {
536                final PackageInstallerSession session = mSessions.valueAt(i);
537                if (Objects.equals(session.installerPackageName, installerPackageName)
538                        && session.userId == userId) {
539                    result.add(session.generateInfo());
540                }
541            }
542        }
543        return result;
544    }
545
546    @Override
547    public void uninstall(String packageName, int flags, IPackageDeleteObserver2 observer,
548            int userId) {
549        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
550
551        // TODO: enforce installer of record or permission
552        mPm.deletePackage(packageName, observer, userId, flags);
553    }
554
555    @Override
556    public void uninstallSplit(String basePackageName, String overlayName, int flags,
557            IPackageDeleteObserver2 observer, int userId) {
558        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit");
559
560        // TODO: flesh out once PM has split support
561        throw new UnsupportedOperationException();
562    }
563
564    @Override
565    public void setPermissionsResult(int sessionId, boolean accepted) {
566        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
567
568        synchronized (mSessions) {
569            mSessions.get(sessionId).setPermissionsResult(accepted);
570        }
571    }
572
573    @Override
574    public void registerCallback(IPackageInstallerCallback callback, int userId) {
575        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
576        enforceCallerCanReadSessions();
577
578        mCallbacks.register(callback, userId);
579    }
580
581    @Override
582    public void unregisterCallback(IPackageInstallerCallback callback) {
583        mCallbacks.unregister(callback);
584    }
585
586    private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
587            int installerUid) {
588        int count = 0;
589        final int size = sessions.size();
590        for (int i = 0; i < size; i++) {
591            final PackageInstallerSession session = sessions.valueAt(i);
592            if (session.installerUid == installerUid) {
593                count++;
594            }
595        }
596        return count;
597    }
598
599    private boolean isCallingUidOwner(PackageInstallerSession session) {
600        final int callingUid = Binder.getCallingUid();
601        if (callingUid == Process.ROOT_UID) {
602            return true;
603        } else {
604            return (session != null) && (callingUid == session.installerUid);
605        }
606    }
607
608    /**
609     * We allow those with permission, or the current home app.
610     */
611    private void enforceCallerCanReadSessions() {
612        final boolean hasPermission = (mContext.checkCallingOrSelfPermission(
613                android.Manifest.permission.READ_INSTALL_SESSIONS)
614                == PackageManager.PERMISSION_GRANTED);
615        final boolean isHomeApp = mPm.checkCallerIsHomeApp();
616        if (hasPermission || isHomeApp) {
617            return;
618        } else {
619            throw new SecurityException("Caller must be current home app to read install sessions");
620        }
621    }
622
623    private static class Callbacks extends Handler {
624        private static final int MSG_SESSION_CREATED = 1;
625        private static final int MSG_SESSION_OPENED = 2;
626        private static final int MSG_SESSION_PROGRESS_CHANGED = 3;
627        private static final int MSG_SESSION_CLOSED = 4;
628        private static final int MSG_SESSION_FINISHED = 5;
629
630        private final RemoteCallbackList<IPackageInstallerCallback>
631                mCallbacks = new RemoteCallbackList<>();
632
633        public Callbacks(Looper looper) {
634            super(looper);
635        }
636
637        public void register(IPackageInstallerCallback callback, int userId) {
638            mCallbacks.register(callback, new UserHandle(userId));
639        }
640
641        public void unregister(IPackageInstallerCallback callback) {
642            mCallbacks.unregister(callback);
643        }
644
645        @Override
646        public void handleMessage(Message msg) {
647            final int userId = msg.arg2;
648            final int n = mCallbacks.beginBroadcast();
649            for (int i = 0; i < n; i++) {
650                final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
651                final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
652                // TODO: dispatch notifications for slave profiles
653                if (userId == user.getIdentifier()) {
654                    try {
655                        invokeCallback(callback, msg);
656                    } catch (RemoteException ignored) {
657                    }
658                }
659            }
660            mCallbacks.finishBroadcast();
661        }
662
663        private void invokeCallback(IPackageInstallerCallback callback, Message msg)
664                throws RemoteException {
665            final int sessionId = msg.arg1;
666            switch (msg.what) {
667                case MSG_SESSION_CREATED:
668                    callback.onSessionCreated(sessionId);
669                    break;
670                case MSG_SESSION_OPENED:
671                    callback.onSessionOpened(sessionId);
672                    break;
673                case MSG_SESSION_PROGRESS_CHANGED:
674                    callback.onSessionProgressChanged(sessionId, (float) msg.obj);
675                    break;
676                case MSG_SESSION_CLOSED:
677                    callback.onSessionClosed(sessionId);
678                    break;
679                case MSG_SESSION_FINISHED:
680                    callback.onSessionFinished(sessionId, (boolean) msg.obj);
681                    break;
682            }
683        }
684
685        private void notifySessionCreated(int sessionId, int userId) {
686            obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
687        }
688
689        private void notifySessionOpened(int sessionId, int userId) {
690            obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget();
691        }
692
693        private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
694            obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
695        }
696
697        private void notifySessionClosed(int sessionId, int userId) {
698            obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget();
699        }
700
701        public void notifySessionFinished(int sessionId, int userId, boolean success) {
702            obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
703        }
704    }
705
706    void dump(IndentingPrintWriter pw) {
707        synchronized (mSessions) {
708            pw.println("Active install sessions:");
709            pw.increaseIndent();
710            int N = mSessions.size();
711            for (int i = 0; i < N; i++) {
712                final PackageInstallerSession session = mSessions.valueAt(i);
713                session.dump(pw);
714                pw.println();
715            }
716            pw.println();
717            pw.decreaseIndent();
718
719            pw.println("Historical install sessions:");
720            pw.increaseIndent();
721            N = mHistoricalSessions.size();
722            for (int i = 0; i < N; i++) {
723                final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
724                session.dump(pw);
725                pw.println();
726            }
727            pw.println();
728            pw.decreaseIndent();
729        }
730    }
731
732    class InternalCallback {
733        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
734            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
735        }
736
737        public void onSessionClosed(PackageInstallerSession session) {
738            mCallbacks.notifySessionClosed(session.sessionId, session.userId);
739        }
740
741        public void onSessionFinished(PackageInstallerSession session, boolean success) {
742            mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
743            synchronized (mSessions) {
744                mSessions.remove(session.sessionId);
745                mHistoricalSessions.put(session.sessionId, session);
746            }
747            writeSessionsAsync();
748        }
749    }
750}
751