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