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