PackageInstallerService.java revision 79c238ada6e7072f62f807781d58d314ed2f17a9
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 org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
20import static org.xmlpull.v1.XmlPullParser.START_TAG;
21
22import android.Manifest;
23import android.app.ActivityManager;
24import android.app.AppGlobals;
25import android.app.AppOpsManager;
26import android.app.Notification;
27import android.app.NotificationManager;
28import android.app.PackageDeleteObserver;
29import android.app.PackageInstallObserver;
30import android.app.admin.DeviceAdminInfo;
31import android.app.admin.DevicePolicyManagerInternal;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentSender;
35import android.content.IntentSender.SendIntentException;
36import android.content.pm.ApplicationInfo;
37import android.content.pm.IPackageInstaller;
38import android.content.pm.IPackageInstallerCallback;
39import android.content.pm.IPackageInstallerSession;
40import android.content.pm.PackageInfo;
41import android.content.pm.PackageInstaller;
42import android.content.pm.PackageInstaller.SessionInfo;
43import android.content.pm.PackageInstaller.SessionParams;
44import android.content.pm.PackageManager;
45import android.content.pm.ParceledListSlice;
46import android.content.pm.VersionedPackage;
47import android.graphics.Bitmap;
48import android.net.Uri;
49import android.os.Binder;
50import android.os.Build;
51import android.os.Bundle;
52import android.os.Environment;
53import android.os.Handler;
54import android.os.HandlerThread;
55import android.os.Looper;
56import android.os.Message;
57import android.os.Process;
58import android.os.RemoteCallbackList;
59import android.os.RemoteException;
60import android.os.SELinux;
61import android.os.UserHandle;
62import android.os.UserManager;
63import android.os.storage.StorageManager;
64import android.system.ErrnoException;
65import android.system.Os;
66import android.text.TextUtils;
67import android.text.format.DateUtils;
68import android.util.ArraySet;
69import android.util.AtomicFile;
70import android.util.ExceptionUtils;
71import android.util.Slog;
72import android.util.SparseArray;
73import android.util.SparseBooleanArray;
74import android.util.SparseIntArray;
75import android.util.Xml;
76
77import com.android.internal.R;
78import com.android.internal.annotations.GuardedBy;
79import com.android.internal.content.PackageHelper;
80import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
81import com.android.internal.notification.SystemNotificationChannels;
82import com.android.internal.util.FastXmlSerializer;
83import com.android.internal.util.ImageUtils;
84import com.android.internal.util.IndentingPrintWriter;
85import com.android.server.IoThread;
86import com.android.server.LocalServices;
87import com.android.server.pm.permission.PermissionManagerInternal;
88
89import libcore.io.IoUtils;
90
91import org.xmlpull.v1.XmlPullParser;
92import org.xmlpull.v1.XmlPullParserException;
93import org.xmlpull.v1.XmlSerializer;
94
95import java.io.CharArrayWriter;
96import java.io.File;
97import java.io.FileInputStream;
98import java.io.FileNotFoundException;
99import java.io.FileOutputStream;
100import java.io.FilenameFilter;
101import java.io.IOException;
102import java.nio.charset.StandardCharsets;
103import java.security.SecureRandom;
104import java.util.ArrayList;
105import java.util.Collections;
106import java.util.List;
107import java.util.Objects;
108import java.util.Random;
109
110public class PackageInstallerService extends IPackageInstaller.Stub {
111    private static final String TAG = "PackageInstaller";
112    private static final boolean LOGD = false;
113
114    // TODO: remove outstanding sessions when installer package goes away
115    // TODO: notify listeners in other users when package has been installed there
116    // TODO: purge expired sessions periodically in addition to at reboot
117
118    /** XML constants used in {@link #mSessionsFile} */
119    private static final String TAG_SESSIONS = "sessions";
120
121    /** Automatically destroy sessions older than this */
122    private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
123    /** Upper bound on number of active sessions for a UID */
124    private static final long MAX_ACTIVE_SESSIONS = 1024;
125    /** Upper bound on number of historical sessions for a UID */
126    private static final long MAX_HISTORICAL_SESSIONS = 1048576;
127
128    private final Context mContext;
129    private final PackageManagerService mPm;
130    private final PermissionManagerInternal mPermissionManager;
131
132    private AppOpsManager mAppOps;
133
134    private final HandlerThread mInstallThread;
135    private final Handler mInstallHandler;
136
137    private final Callbacks mCallbacks;
138
139    /**
140     * File storing persisted {@link #mSessions} metadata.
141     */
142    private final AtomicFile mSessionsFile;
143
144    /**
145     * Directory storing persisted {@link #mSessions} metadata which is too
146     * heavy to store directly in {@link #mSessionsFile}.
147     */
148    private final File mSessionsDir;
149
150    private final InternalCallback mInternalCallback = new InternalCallback();
151
152    /**
153     * Used for generating session IDs. Since this is created at boot time,
154     * normal random might be predictable.
155     */
156    private final Random mRandom = new SecureRandom();
157
158    /** All sessions allocated */
159    @GuardedBy("mSessions")
160    private final SparseBooleanArray mAllocatedSessions = new SparseBooleanArray();
161
162    @GuardedBy("mSessions")
163    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
164
165    /** Historical sessions kept around for debugging purposes */
166    @GuardedBy("mSessions")
167    private final List<String> mHistoricalSessions = new ArrayList<>();
168
169    @GuardedBy("mSessions")
170    private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
171
172    /** Sessions allocated to legacy users */
173    @GuardedBy("mSessions")
174    private final SparseBooleanArray mLegacySessions = new SparseBooleanArray();
175
176    private static final FilenameFilter sStageFilter = new FilenameFilter() {
177        @Override
178        public boolean accept(File dir, String name) {
179            return isStageName(name);
180        }
181    };
182
183    public PackageInstallerService(Context context, PackageManagerService pm) {
184        mContext = context;
185        mPm = pm;
186        mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
187
188        mInstallThread = new HandlerThread(TAG);
189        mInstallThread.start();
190
191        mInstallHandler = new Handler(mInstallThread.getLooper());
192
193        mCallbacks = new Callbacks(mInstallThread.getLooper());
194
195        mSessionsFile = new AtomicFile(
196                new File(Environment.getDataSystemDirectory(), "install_sessions.xml"));
197        mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
198        mSessionsDir.mkdirs();
199    }
200
201    public void systemReady() {
202        mAppOps = mContext.getSystemService(AppOpsManager.class);
203
204        synchronized (mSessions) {
205            readSessionsLocked();
206
207            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/);
208            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/);
209
210            final ArraySet<File> unclaimedIcons = newArraySet(
211                    mSessionsDir.listFiles());
212
213            // Ignore stages and icons claimed by active sessions
214            for (int i = 0; i < mSessions.size(); i++) {
215                final PackageInstallerSession session = mSessions.valueAt(i);
216                unclaimedIcons.remove(buildAppIconFile(session.sessionId));
217            }
218
219            // Clean up orphaned icons
220            for (File icon : unclaimedIcons) {
221                Slog.w(TAG, "Deleting orphan icon " + icon);
222                icon.delete();
223            }
224        }
225    }
226
227    private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) {
228        final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
229        final ArraySet<File> unclaimedStages = newArraySet(
230                stagingDir.listFiles(sStageFilter));
231
232        // Ignore stages claimed by active sessions
233        for (int i = 0; i < mSessions.size(); i++) {
234            final PackageInstallerSession session = mSessions.valueAt(i);
235            unclaimedStages.remove(session.stageDir);
236        }
237
238        // Clean up orphaned staging directories
239        for (File stage : unclaimedStages) {
240            Slog.w(TAG, "Deleting orphan stage " + stage);
241            synchronized (mPm.mInstallLock) {
242                mPm.removeCodePathLI(stage);
243            }
244        }
245    }
246
247    public void onPrivateVolumeMounted(String volumeUuid) {
248        synchronized (mSessions) {
249            reconcileStagesLocked(volumeUuid, false /*isInstant*/);
250        }
251    }
252
253    public static boolean isStageName(String name) {
254        final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
255        final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
256        final boolean isLegacyContainer = name.startsWith("smdl2tmp");
257        return isFile || isContainer || isLegacyContainer;
258    }
259
260    @Deprecated
261    public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException {
262        synchronized (mSessions) {
263            try {
264                final int sessionId = allocateSessionIdLocked();
265                mLegacySessions.put(sessionId, true);
266                final File stageDir = buildStageDir(volumeUuid, sessionId, isEphemeral);
267                prepareStageDir(stageDir);
268                return stageDir;
269            } catch (IllegalStateException e) {
270                throw new IOException(e);
271            }
272        }
273    }
274
275    @Deprecated
276    public String allocateExternalStageCidLegacy() {
277        synchronized (mSessions) {
278            final int sessionId = allocateSessionIdLocked();
279            mLegacySessions.put(sessionId, true);
280            return "smdl" + sessionId + ".tmp";
281        }
282    }
283
284    private void readSessionsLocked() {
285        if (LOGD) Slog.v(TAG, "readSessionsLocked()");
286
287        mSessions.clear();
288
289        FileInputStream fis = null;
290        try {
291            fis = mSessionsFile.openRead();
292            final XmlPullParser in = Xml.newPullParser();
293            in.setInput(fis, StandardCharsets.UTF_8.name());
294
295            int type;
296            while ((type = in.next()) != END_DOCUMENT) {
297                if (type == START_TAG) {
298                    final String tag = in.getName();
299                    if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
300                        final PackageInstallerSession session;
301                        try {
302                            session = PackageInstallerSession.readFromXml(in, mInternalCallback,
303                                    mContext, mPm, mInstallThread.getLooper(), mSessionsDir);
304                        } catch (Exception e) {
305                            Slog.e(TAG, "Could not read session", e);
306                            continue;
307                        }
308
309                        final long age = System.currentTimeMillis() - session.createdMillis;
310
311                        final boolean valid;
312                        if (age >= MAX_AGE_MILLIS) {
313                            Slog.w(TAG, "Abandoning old session first created at "
314                                    + session.createdMillis);
315                            valid = false;
316                        } else {
317                            valid = true;
318                        }
319
320                        if (valid) {
321                            mSessions.put(session.sessionId, session);
322                        } else {
323                            // Since this is early during boot we don't send
324                            // any observer events about the session, but we
325                            // keep details around for dumpsys.
326                            addHistoricalSessionLocked(session);
327                        }
328                        mAllocatedSessions.put(session.sessionId, true);
329                    }
330                }
331            }
332        } catch (FileNotFoundException e) {
333            // Missing sessions are okay, probably first boot
334        } catch (IOException | XmlPullParserException e) {
335            Slog.wtf(TAG, "Failed reading install sessions", e);
336        } finally {
337            IoUtils.closeQuietly(fis);
338        }
339    }
340
341    private void addHistoricalSessionLocked(PackageInstallerSession session) {
342        CharArrayWriter writer = new CharArrayWriter();
343        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
344        session.dump(pw);
345        mHistoricalSessions.add(writer.toString());
346
347        int installerUid = session.getInstallerUid();
348        // Increment the number of sessions by this installerUid.
349        mHistoricalSessionsByInstaller.put(installerUid,
350                mHistoricalSessionsByInstaller.get(installerUid) + 1);
351    }
352
353    private void writeSessionsLocked() {
354        if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
355
356        FileOutputStream fos = null;
357        try {
358            fos = mSessionsFile.startWrite();
359
360            XmlSerializer out = new FastXmlSerializer();
361            out.setOutput(fos, StandardCharsets.UTF_8.name());
362            out.startDocument(null, true);
363            out.startTag(null, TAG_SESSIONS);
364            final int size = mSessions.size();
365            for (int i = 0; i < size; i++) {
366                final PackageInstallerSession session = mSessions.valueAt(i);
367                session.write(out, mSessionsDir);
368            }
369            out.endTag(null, TAG_SESSIONS);
370            out.endDocument();
371
372            mSessionsFile.finishWrite(fos);
373        } catch (IOException e) {
374            if (fos != null) {
375                mSessionsFile.failWrite(fos);
376            }
377        }
378    }
379
380    private File buildAppIconFile(int sessionId) {
381        return new File(mSessionsDir, "app_icon." + sessionId + ".png");
382    }
383
384    private void writeSessionsAsync() {
385        IoThread.getHandler().post(new Runnable() {
386            @Override
387            public void run() {
388                synchronized (mSessions) {
389                    writeSessionsLocked();
390                }
391            }
392        });
393    }
394
395    @Override
396    public int createSession(SessionParams params, String installerPackageName, int userId) {
397        try {
398            return createSessionInternal(params, installerPackageName, userId);
399        } catch (IOException e) {
400            throw ExceptionUtils.wrap(e);
401        }
402    }
403
404    private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
405            throws IOException {
406        final int callingUid = Binder.getCallingUid();
407        mPermissionManager.enforceCrossUserPermission(
408                callingUid, userId, true, true, "createSession");
409
410        if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
411            throw new SecurityException("User restriction prevents installing");
412        }
413
414        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
415            params.installFlags |= PackageManager.INSTALL_FROM_ADB;
416
417        } else {
418            // Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
419            // caller.
420            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
421                    PackageManager.PERMISSION_GRANTED) {
422                mAppOps.checkPackage(callingUid, installerPackageName);
423            }
424
425            params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
426            params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
427            params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
428            if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
429                    && !mPm.isCallerVerifier(callingUid)) {
430                params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
431            }
432        }
433
434        // Only system components can circumvent runtime permissions when installing.
435        if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
436                && mContext.checkCallingOrSelfPermission(Manifest.permission
437                .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
438            throw new SecurityException("You need the "
439                    + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
440                    + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
441        }
442
443        if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
444                || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
445            throw new IllegalArgumentException(
446                    "New installs into ASEC containers no longer supported");
447        }
448
449        // Defensively resize giant app icons
450        if (params.appIcon != null) {
451            final ActivityManager am = (ActivityManager) mContext.getSystemService(
452                    Context.ACTIVITY_SERVICE);
453            final int iconSize = am.getLauncherLargeIconSize();
454            if ((params.appIcon.getWidth() > iconSize * 2)
455                    || (params.appIcon.getHeight() > iconSize * 2)) {
456                params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
457                        true);
458            }
459        }
460
461        switch (params.mode) {
462            case SessionParams.MODE_FULL_INSTALL:
463            case SessionParams.MODE_INHERIT_EXISTING:
464                break;
465            default:
466                throw new IllegalArgumentException("Invalid install mode: " + params.mode);
467        }
468
469        // If caller requested explicit location, sanity check it, otherwise
470        // resolve the best internal or adopted location.
471        if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
472            if (!PackageHelper.fitsOnInternal(mContext, params)) {
473                throw new IOException("No suitable internal storage available");
474            }
475
476        } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
477            if (!PackageHelper.fitsOnExternal(mContext, params)) {
478                throw new IOException("No suitable external storage available");
479            }
480
481        } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
482            // For now, installs to adopted media are treated as internal from
483            // an install flag point-of-view.
484            params.setInstallFlagsInternal();
485
486        } else {
487            // For now, installs to adopted media are treated as internal from
488            // an install flag point-of-view.
489            params.setInstallFlagsInternal();
490
491            // Resolve best location for install, based on combination of
492            // requested install flags, delta size, and manifest settings.
493            final long ident = Binder.clearCallingIdentity();
494            try {
495                params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
496            } finally {
497                Binder.restoreCallingIdentity(ident);
498            }
499        }
500
501        final int sessionId;
502        final PackageInstallerSession session;
503        synchronized (mSessions) {
504            // Sanity check that installer isn't going crazy
505            final int activeCount = getSessionCount(mSessions, callingUid);
506            if (activeCount >= MAX_ACTIVE_SESSIONS) {
507                throw new IllegalStateException(
508                        "Too many active sessions for UID " + callingUid);
509            }
510            final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
511            if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
512                throw new IllegalStateException(
513                        "Too many historical sessions for UID " + callingUid);
514            }
515
516            sessionId = allocateSessionIdLocked();
517        }
518
519        final long createdMillis = System.currentTimeMillis();
520        // We're staging to exactly one location
521        File stageDir = null;
522        String stageCid = null;
523        if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
524            final boolean isInstant =
525                    (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
526            stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
527        } else {
528            stageCid = buildExternalStageCid(sessionId);
529        }
530
531        session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
532                mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
533                params, createdMillis, stageDir, stageCid, false, false);
534
535        synchronized (mSessions) {
536            mSessions.put(sessionId, session);
537        }
538
539        mCallbacks.notifySessionCreated(session.sessionId, session.userId);
540        writeSessionsAsync();
541        return sessionId;
542    }
543
544    @Override
545    public void updateSessionAppIcon(int sessionId, Bitmap appIcon) {
546        synchronized (mSessions) {
547            final PackageInstallerSession session = mSessions.get(sessionId);
548            if (session == null || !isCallingUidOwner(session)) {
549                throw new SecurityException("Caller has no access to session " + sessionId);
550            }
551
552            // Defensively resize giant app icons
553            if (appIcon != null) {
554                final ActivityManager am = (ActivityManager) mContext.getSystemService(
555                        Context.ACTIVITY_SERVICE);
556                final int iconSize = am.getLauncherLargeIconSize();
557                if ((appIcon.getWidth() > iconSize * 2)
558                        || (appIcon.getHeight() > iconSize * 2)) {
559                    appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true);
560                }
561            }
562
563            session.params.appIcon = appIcon;
564            session.params.appIconLastModified = -1;
565
566            mInternalCallback.onSessionBadgingChanged(session);
567        }
568    }
569
570    @Override
571    public void updateSessionAppLabel(int sessionId, String appLabel) {
572        synchronized (mSessions) {
573            final PackageInstallerSession session = mSessions.get(sessionId);
574            if (session == null || !isCallingUidOwner(session)) {
575                throw new SecurityException("Caller has no access to session " + sessionId);
576            }
577            session.params.appLabel = appLabel;
578            mInternalCallback.onSessionBadgingChanged(session);
579        }
580    }
581
582    @Override
583    public void abandonSession(int sessionId) {
584        synchronized (mSessions) {
585            final PackageInstallerSession session = mSessions.get(sessionId);
586            if (session == null || !isCallingUidOwner(session)) {
587                throw new SecurityException("Caller has no access to session " + sessionId);
588            }
589            session.abandon();
590        }
591    }
592
593    @Override
594    public IPackageInstallerSession openSession(int sessionId) {
595        try {
596            return openSessionInternal(sessionId);
597        } catch (IOException e) {
598            throw ExceptionUtils.wrap(e);
599        }
600    }
601
602    private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
603        synchronized (mSessions) {
604            final PackageInstallerSession session = mSessions.get(sessionId);
605            if (session == null || !isCallingUidOwner(session)) {
606                throw new SecurityException("Caller has no access to session " + sessionId);
607            }
608            session.open();
609            return session;
610        }
611    }
612
613    private int allocateSessionIdLocked() {
614        int n = 0;
615        int sessionId;
616        do {
617            sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
618            if (!mAllocatedSessions.get(sessionId, false)) {
619                mAllocatedSessions.put(sessionId, true);
620                return sessionId;
621            }
622        } while (n++ < 32);
623
624        throw new IllegalStateException("Failed to allocate session ID");
625    }
626
627    private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
628        return Environment.getDataAppDirectory(volumeUuid);
629    }
630
631    private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
632        final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
633        return new File(stagingDir, "vmdl" + sessionId + ".tmp");
634    }
635
636    static void prepareStageDir(File stageDir) throws IOException {
637        if (stageDir.exists()) {
638            throw new IOException("Session dir already exists: " + stageDir);
639        }
640
641        try {
642            Os.mkdir(stageDir.getAbsolutePath(), 0755);
643            Os.chmod(stageDir.getAbsolutePath(), 0755);
644        } catch (ErrnoException e) {
645            // This purposefully throws if directory already exists
646            throw new IOException("Failed to prepare session dir: " + stageDir, e);
647        }
648
649        if (!SELinux.restorecon(stageDir)) {
650            throw new IOException("Failed to restorecon session dir: " + stageDir);
651        }
652    }
653
654    private String buildExternalStageCid(int sessionId) {
655        return "smdl" + sessionId + ".tmp";
656    }
657
658    @Override
659    public SessionInfo getSessionInfo(int sessionId) {
660        synchronized (mSessions) {
661            final PackageInstallerSession session = mSessions.get(sessionId);
662            return session != null ? session.generateInfo() : null;
663        }
664    }
665
666    @Override
667    public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
668        mPermissionManager.enforceCrossUserPermission(
669                Binder.getCallingUid(), userId, true, false, "getAllSessions");
670
671        final List<SessionInfo> result = new ArrayList<>();
672        synchronized (mSessions) {
673            for (int i = 0; i < mSessions.size(); i++) {
674                final PackageInstallerSession session = mSessions.valueAt(i);
675                if (session.userId == userId) {
676                    result.add(session.generateInfo(false));
677                }
678            }
679        }
680        return new ParceledListSlice<>(result);
681    }
682
683    @Override
684    public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
685        mPermissionManager.enforceCrossUserPermission(
686                Binder.getCallingUid(), userId, true, false, "getMySessions");
687        mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
688
689        final List<SessionInfo> result = new ArrayList<>();
690        synchronized (mSessions) {
691            for (int i = 0; i < mSessions.size(); i++) {
692                final PackageInstallerSession session = mSessions.valueAt(i);
693
694                SessionInfo info = session.generateInfo(false);
695                if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
696                        && session.userId == userId) {
697                    result.add(info);
698                }
699            }
700        }
701        return new ParceledListSlice<>(result);
702    }
703
704    @Override
705    public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
706                IntentSender statusReceiver, int userId) throws RemoteException {
707        final int callingUid = Binder.getCallingUid();
708        mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
709        if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
710            mAppOps.checkPackage(callingUid, callerPackageName);
711        }
712
713        // Check whether the caller is device owner or affiliated profile owner, in which case we do
714        // it silently.
715        final int callingUserId = UserHandle.getUserId(callingUid);
716        DevicePolicyManagerInternal dpmi =
717                LocalServices.getService(DevicePolicyManagerInternal.class);
718        final boolean isDeviceOwnerOrAffiliatedProfileOwner =
719                dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
720                        DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)
721                        && dpmi.isUserAffiliatedWithDevice(callingUserId);
722
723        final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
724                statusReceiver, versionedPackage.getPackageName(),
725                isDeviceOwnerOrAffiliatedProfileOwner, userId);
726        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
727                    == PackageManager.PERMISSION_GRANTED) {
728            // Sweet, call straight through!
729            mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
730        } else if (isDeviceOwnerOrAffiliatedProfileOwner) {
731            // Allow the device owner and affiliated profile owner to silently delete packages
732            // Need to clear the calling identity to get DELETE_PACKAGES permission
733            long ident = Binder.clearCallingIdentity();
734            try {
735                mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
736            } finally {
737                Binder.restoreCallingIdentity(ident);
738            }
739        } else {
740            ApplicationInfo appInfo = mPm.getApplicationInfo(callerPackageName, 0, userId);
741            if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
742                mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
743                        null);
744            }
745
746            // Take a short detour to confirm with user
747            final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
748            intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
749            intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
750            adapter.onUserActionRequired(intent);
751        }
752    }
753
754    @Override
755    public void setPermissionsResult(int sessionId, boolean accepted) {
756        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
757
758        synchronized (mSessions) {
759            PackageInstallerSession session = mSessions.get(sessionId);
760            if (session != null) {
761                session.setPermissionsResult(accepted);
762            }
763        }
764    }
765
766    @Override
767    public void registerCallback(IPackageInstallerCallback callback, int userId) {
768        mPermissionManager.enforceCrossUserPermission(
769                Binder.getCallingUid(), userId, true, false, "registerCallback");
770        mCallbacks.register(callback, userId);
771    }
772
773    @Override
774    public void unregisterCallback(IPackageInstallerCallback callback) {
775        mCallbacks.unregister(callback);
776    }
777
778    private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
779            int installerUid) {
780        int count = 0;
781        final int size = sessions.size();
782        for (int i = 0; i < size; i++) {
783            final PackageInstallerSession session = sessions.valueAt(i);
784            if (session.getInstallerUid() == installerUid) {
785                count++;
786            }
787        }
788        return count;
789    }
790
791    private boolean isCallingUidOwner(PackageInstallerSession session) {
792        final int callingUid = Binder.getCallingUid();
793        if (callingUid == Process.ROOT_UID) {
794            return true;
795        } else {
796            return (session != null) && (callingUid == session.getInstallerUid());
797        }
798    }
799
800    static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
801        private final Context mContext;
802        private final IntentSender mTarget;
803        private final String mPackageName;
804        private final Notification mNotification;
805
806        public PackageDeleteObserverAdapter(Context context, IntentSender target,
807                String packageName, boolean showNotification, int userId) {
808            mContext = context;
809            mTarget = target;
810            mPackageName = packageName;
811            if (showNotification) {
812                mNotification = buildSuccessNotification(mContext,
813                        mContext.getResources().getString(R.string.package_deleted_device_owner),
814                        packageName,
815                        userId);
816            } else {
817                mNotification = null;
818            }
819        }
820
821        @Override
822        public void onUserActionRequired(Intent intent) {
823            final Intent fillIn = new Intent();
824            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
825            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
826                    PackageInstaller.STATUS_PENDING_USER_ACTION);
827            fillIn.putExtra(Intent.EXTRA_INTENT, intent);
828            try {
829                mTarget.sendIntent(mContext, 0, fillIn, null, null);
830            } catch (SendIntentException ignored) {
831            }
832        }
833
834        @Override
835        public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
836            if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) {
837                NotificationManager notificationManager = (NotificationManager)
838                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
839                notificationManager.notify(basePackageName,
840                        SystemMessage.NOTE_PACKAGE_STATE,
841                        mNotification);
842            }
843            final Intent fillIn = new Intent();
844            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
845            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
846                    PackageManager.deleteStatusToPublicStatus(returnCode));
847            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
848                    PackageManager.deleteStatusToString(returnCode, msg));
849            fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
850            try {
851                mTarget.sendIntent(mContext, 0, fillIn, null, null);
852            } catch (SendIntentException ignored) {
853            }
854        }
855    }
856
857    static class PackageInstallObserverAdapter extends PackageInstallObserver {
858        private final Context mContext;
859        private final IntentSender mTarget;
860        private final int mSessionId;
861        private final boolean mShowNotification;
862        private final int mUserId;
863
864        public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
865                boolean showNotification, int userId) {
866            mContext = context;
867            mTarget = target;
868            mSessionId = sessionId;
869            mShowNotification = showNotification;
870            mUserId = userId;
871        }
872
873        @Override
874        public void onUserActionRequired(Intent intent) {
875            final Intent fillIn = new Intent();
876            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
877            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
878                    PackageInstaller.STATUS_PENDING_USER_ACTION);
879            fillIn.putExtra(Intent.EXTRA_INTENT, intent);
880            try {
881                mTarget.sendIntent(mContext, 0, fillIn, null, null);
882            } catch (SendIntentException ignored) {
883            }
884        }
885
886        @Override
887        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
888                Bundle extras) {
889            if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
890                boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
891                Notification notification = buildSuccessNotification(mContext,
892                        mContext.getResources()
893                                .getString(update ? R.string.package_updated_device_owner :
894                                        R.string.package_installed_device_owner),
895                        basePackageName,
896                        mUserId);
897                if (notification != null) {
898                    NotificationManager notificationManager = (NotificationManager)
899                            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
900                    notificationManager.notify(basePackageName,
901                            SystemMessage.NOTE_PACKAGE_STATE,
902                            notification);
903                }
904            }
905            final Intent fillIn = new Intent();
906            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);
907            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
908            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
909                    PackageManager.installStatusToPublicStatus(returnCode));
910            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
911                    PackageManager.installStatusToString(returnCode, msg));
912            fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
913            if (extras != null) {
914                final String existing = extras.getString(
915                        PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
916                if (!TextUtils.isEmpty(existing)) {
917                    fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
918                }
919            }
920            try {
921                mTarget.sendIntent(mContext, 0, fillIn, null, null);
922            } catch (SendIntentException ignored) {
923            }
924        }
925    }
926
927    /**
928     * Build a notification for package installation / deletion by device owners that is shown if
929     * the operation succeeds.
930     */
931    private static Notification buildSuccessNotification(Context context, String contentText,
932            String basePackageName, int userId) {
933        PackageInfo packageInfo = null;
934        try {
935            packageInfo = AppGlobals.getPackageManager().getPackageInfo(
936                    basePackageName, PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
937        } catch (RemoteException ignored) {
938        }
939        if (packageInfo == null || packageInfo.applicationInfo == null) {
940            Slog.w(TAG, "Notification not built for package: " + basePackageName);
941            return null;
942        }
943        PackageManager pm = context.getPackageManager();
944        Bitmap packageIcon = ImageUtils.buildScaledBitmap(
945                packageInfo.applicationInfo.loadIcon(pm),
946                context.getResources().getDimensionPixelSize(
947                        android.R.dimen.notification_large_icon_width),
948                context.getResources().getDimensionPixelSize(
949                        android.R.dimen.notification_large_icon_height));
950        CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm);
951        return new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
952                .setSmallIcon(R.drawable.ic_check_circle_24px)
953                .setColor(context.getResources().getColor(
954                        R.color.system_notification_accent_color))
955                .setContentTitle(packageLabel)
956                .setContentText(contentText)
957                .setStyle(new Notification.BigTextStyle().bigText(contentText))
958                .setLargeIcon(packageIcon)
959                .build();
960    }
961
962    public static <E> ArraySet<E> newArraySet(E... elements) {
963        final ArraySet<E> set = new ArraySet<E>();
964        if (elements != null) {
965            set.ensureCapacity(elements.length);
966            Collections.addAll(set, elements);
967        }
968        return set;
969    }
970
971    private static class Callbacks extends Handler {
972        private static final int MSG_SESSION_CREATED = 1;
973        private static final int MSG_SESSION_BADGING_CHANGED = 2;
974        private static final int MSG_SESSION_ACTIVE_CHANGED = 3;
975        private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
976        private static final int MSG_SESSION_FINISHED = 5;
977
978        private final RemoteCallbackList<IPackageInstallerCallback>
979                mCallbacks = new RemoteCallbackList<>();
980
981        public Callbacks(Looper looper) {
982            super(looper);
983        }
984
985        public void register(IPackageInstallerCallback callback, int userId) {
986            mCallbacks.register(callback, new UserHandle(userId));
987        }
988
989        public void unregister(IPackageInstallerCallback callback) {
990            mCallbacks.unregister(callback);
991        }
992
993        @Override
994        public void handleMessage(Message msg) {
995            final int userId = msg.arg2;
996            final int n = mCallbacks.beginBroadcast();
997            for (int i = 0; i < n; i++) {
998                final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
999                final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
1000                // TODO: dispatch notifications for slave profiles
1001                if (userId == user.getIdentifier()) {
1002                    try {
1003                        invokeCallback(callback, msg);
1004                    } catch (RemoteException ignored) {
1005                    }
1006                }
1007            }
1008            mCallbacks.finishBroadcast();
1009        }
1010
1011        private void invokeCallback(IPackageInstallerCallback callback, Message msg)
1012                throws RemoteException {
1013            final int sessionId = msg.arg1;
1014            switch (msg.what) {
1015                case MSG_SESSION_CREATED:
1016                    callback.onSessionCreated(sessionId);
1017                    break;
1018                case MSG_SESSION_BADGING_CHANGED:
1019                    callback.onSessionBadgingChanged(sessionId);
1020                    break;
1021                case MSG_SESSION_ACTIVE_CHANGED:
1022                    callback.onSessionActiveChanged(sessionId, (boolean) msg.obj);
1023                    break;
1024                case MSG_SESSION_PROGRESS_CHANGED:
1025                    callback.onSessionProgressChanged(sessionId, (float) msg.obj);
1026                    break;
1027                case MSG_SESSION_FINISHED:
1028                    callback.onSessionFinished(sessionId, (boolean) msg.obj);
1029                    break;
1030            }
1031        }
1032
1033        private void notifySessionCreated(int sessionId, int userId) {
1034            obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
1035        }
1036
1037        private void notifySessionBadgingChanged(int sessionId, int userId) {
1038            obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget();
1039        }
1040
1041        private void notifySessionActiveChanged(int sessionId, int userId, boolean active) {
1042            obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget();
1043        }
1044
1045        private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
1046            obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
1047        }
1048
1049        public void notifySessionFinished(int sessionId, int userId, boolean success) {
1050            obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
1051        }
1052    }
1053
1054    void dump(IndentingPrintWriter pw) {
1055        synchronized (mSessions) {
1056            pw.println("Active install sessions:");
1057            pw.increaseIndent();
1058            int N = mSessions.size();
1059            for (int i = 0; i < N; i++) {
1060                final PackageInstallerSession session = mSessions.valueAt(i);
1061                session.dump(pw);
1062                pw.println();
1063            }
1064            pw.println();
1065            pw.decreaseIndent();
1066
1067            pw.println("Historical install sessions:");
1068            pw.increaseIndent();
1069            N = mHistoricalSessions.size();
1070            for (int i = 0; i < N; i++) {
1071                pw.print(mHistoricalSessions.get(i));
1072                pw.println();
1073            }
1074            pw.println();
1075            pw.decreaseIndent();
1076
1077            pw.println("Legacy install sessions:");
1078            pw.increaseIndent();
1079            pw.println(mLegacySessions.toString());
1080            pw.decreaseIndent();
1081        }
1082    }
1083
1084    class InternalCallback {
1085        public void onSessionBadgingChanged(PackageInstallerSession session) {
1086            mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
1087            writeSessionsAsync();
1088        }
1089
1090        public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
1091            mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active);
1092        }
1093
1094        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
1095            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
1096        }
1097
1098        public void onSessionFinished(final PackageInstallerSession session, boolean success) {
1099            mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
1100
1101            mInstallHandler.post(new Runnable() {
1102                @Override
1103                public void run() {
1104                    synchronized (mSessions) {
1105                        mSessions.remove(session.sessionId);
1106                        addHistoricalSessionLocked(session);
1107
1108                        final File appIconFile = buildAppIconFile(session.sessionId);
1109                        if (appIconFile.exists()) {
1110                            appIconFile.delete();
1111                        }
1112
1113                        writeSessionsLocked();
1114                    }
1115                }
1116            });
1117        }
1118
1119        public void onSessionPrepared(PackageInstallerSession session) {
1120            // We prepared the destination to write into; we want to persist
1121            // this, but it's not critical enough to block for.
1122            writeSessionsAsync();
1123        }
1124
1125        public void onSessionSealedBlocking(PackageInstallerSession session) {
1126            // It's very important that we block until we've recorded the
1127            // session as being sealed, since we never want to allow mutation
1128            // after sealing.
1129            synchronized (mSessions) {
1130                writeSessionsLocked();
1131            }
1132        }
1133    }
1134}
1135