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