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