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