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