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