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