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