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