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