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