PackageInstallerService.java revision e812d9096915ad165de125520ed7371009587d1f
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 long ident = Binder.clearCallingIdentity();
489            try {
490                final int resolved = PackageHelper.resolveInstallLocation(mContext,
491                        params.appPackageName, params.installLocation, params.sizeBytes,
492                        params.installFlags);
493
494                if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) {
495                    stageInternal = true;
496                } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
497                    stageInternal = false;
498                } else {
499                    throw new IOException("No storage with enough free space; res=" + resolved);
500                }
501            } finally {
502                Binder.restoreCallingIdentity(ident);
503            }
504        } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
505            // We always stage inheriting sessions on internal storage first,
506            // since we don't want to grow containers until we're sure that
507            // everything looks legit.
508            stageInternal = true;
509            checkInternalStorage(params.sizeBytes);
510
511            // If we have a good hunch we'll end up on external storage, verify
512            // free space there too.
513            final ApplicationInfo info = mPm.getApplicationInfo(params.appPackageName, 0,
514                    userId);
515            if (info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
516                checkExternalStorage(params.sizeBytes);
517
518                throw new UnsupportedOperationException("TODO: finish fleshing out ASEC support");
519            }
520
521        } else {
522            throw new IllegalArgumentException("Invalid install mode: " + params.mode);
523        }
524
525        final int sessionId;
526        final PackageInstallerSession session;
527        synchronized (mSessions) {
528            // Sanity check that installer isn't going crazy
529            final int activeCount = getSessionCount(mSessions, callingUid);
530            if (activeCount >= MAX_ACTIVE_SESSIONS) {
531                throw new IllegalStateException(
532                        "Too many active sessions for UID " + callingUid);
533            }
534            final int historicalCount = getSessionCount(mHistoricalSessions, callingUid);
535            if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
536                throw new IllegalStateException(
537                        "Too many historical sessions for UID " + callingUid);
538            }
539
540            final long createdMillis = System.currentTimeMillis();
541            sessionId = allocateSessionIdLocked();
542
543            // We're staging to exactly one location
544            File stageDir = null;
545            String stageCid = null;
546            if (stageInternal) {
547                stageDir = prepareInternalStageDir(sessionId);
548            } else {
549                stageCid = prepareExternalStageCid(sessionId, params.sizeBytes);
550            }
551
552            session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
553                    mInstallThread.getLooper(), sessionId, userId, installerPackageName, params,
554                    createdMillis, stageDir, stageCid, false);
555            mSessions.put(sessionId, session);
556        }
557
558        mCallbacks.notifySessionCreated(session.sessionId, session.userId);
559        writeSessionsAsync();
560        return sessionId;
561    }
562
563    private void checkInternalStorage(long sizeBytes) throws IOException {
564        if (sizeBytes <= 0) return;
565
566        final File target = Environment.getDataDirectory();
567        final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target);
568
569        mPm.freeStorage(targetBytes);
570        if (target.getUsableSpace() < targetBytes) {
571            throw new IOException("Not enough internal space to write " + sizeBytes + " bytes");
572        }
573    }
574
575    private void checkExternalStorage(long sizeBytes) throws IOException {
576        if (sizeBytes <= 0) return;
577
578        final File target = new UserEnvironment(UserHandle.USER_OWNER)
579                .getExternalStorageDirectory();
580        final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target);
581
582        if (target.getUsableSpace() < targetBytes) {
583            throw new IOException("Not enough external space to write " + sizeBytes + " bytes");
584        }
585    }
586
587    @Override
588    public IPackageInstallerSession openSession(int sessionId) {
589        synchronized (mSessions) {
590            final PackageInstallerSession session = mSessions.get(sessionId);
591            if (session == null) {
592                throw new IllegalStateException("Missing session " + sessionId);
593            }
594            if (!isCallingUidOwner(session)) {
595                throw new SecurityException("Caller has no access to session " + sessionId);
596            }
597            session.open();
598            return session;
599        }
600    }
601
602    private int allocateSessionIdLocked() {
603        int n = 0;
604        int sessionId;
605        do {
606            sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
607            if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null
608                    && !mLegacySessions.get(sessionId, false)) {
609                return sessionId;
610            }
611        } while (n++ < 32);
612
613        throw new IllegalStateException("Failed to allocate session ID");
614    }
615
616    private File prepareInternalStageDir(int sessionId) throws IOException {
617        final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
618
619        if (file.exists()) {
620            throw new IOException("Session dir already exists: " + file);
621        }
622
623        try {
624            Os.mkdir(file.getAbsolutePath(), 0755);
625            Os.chmod(file.getAbsolutePath(), 0755);
626        } catch (ErrnoException e) {
627            // This purposefully throws if directory already exists
628            throw new IOException("Failed to prepare session dir", e);
629        }
630
631        if (!SELinux.restorecon(file)) {
632            throw new IOException("Failed to restorecon session dir");
633        }
634
635        return file;
636    }
637
638    private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException {
639        if (sizeBytes <= 0) {
640            throw new IOException("Session must provide valid size for ASEC");
641        }
642
643        final String cid = "smdl" + sessionId + ".tmp";
644
645        // Round up to nearest MB, plus another MB for filesystem overhead
646        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
647
648        if (PackageHelper.createSdDir(sizeMb, cid, PackageManagerService.getEncryptKey(),
649                Process.SYSTEM_UID, true) == null) {
650            throw new IOException("Failed to create ASEC");
651        }
652
653        return cid;
654    }
655
656    @Override
657    public SessionInfo getSessionInfo(int sessionId) {
658        synchronized (mSessions) {
659            final PackageInstallerSession session = mSessions.get(sessionId);
660            if (!isCallingUidOwner(session)) {
661                enforceCallerCanReadSessions();
662            }
663            return session != null ? session.generateInfo() : null;
664        }
665    }
666
667    @Override
668    public List<SessionInfo> getAllSessions(int userId) {
669        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions");
670        enforceCallerCanReadSessions();
671
672        final List<SessionInfo> result = new ArrayList<>();
673        synchronized (mSessions) {
674            for (int i = 0; i < mSessions.size(); i++) {
675                final PackageInstallerSession session = mSessions.valueAt(i);
676                if (session.userId == userId) {
677                    result.add(session.generateInfo());
678                }
679            }
680        }
681        return result;
682    }
683
684    @Override
685    public List<SessionInfo> getMySessions(String installerPackageName, int userId) {
686        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions");
687        mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
688
689        final List<SessionInfo> result = new ArrayList<>();
690        synchronized (mSessions) {
691            for (int i = 0; i < mSessions.size(); i++) {
692                final PackageInstallerSession session = mSessions.valueAt(i);
693                if (Objects.equals(session.installerPackageName, installerPackageName)
694                        && session.userId == userId) {
695                    result.add(session.generateInfo());
696                }
697            }
698        }
699        return result;
700    }
701
702    @Override
703    public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) {
704        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
705
706        final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
707                statusReceiver, packageName);
708        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
709                == PackageManager.PERMISSION_GRANTED) {
710            // Sweet, call straight through!
711            mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
712
713        } else {
714            // Take a short detour to confirm with user
715            final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
716            intent.setData(Uri.fromParts("package", packageName, null));
717            intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
718            adapter.onUserActionRequired(intent);
719        }
720    }
721
722    @Override
723    public void setPermissionsResult(int sessionId, boolean accepted) {
724        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
725
726        synchronized (mSessions) {
727            mSessions.get(sessionId).setPermissionsResult(accepted);
728        }
729    }
730
731    @Override
732    public void registerCallback(IPackageInstallerCallback callback, int userId) {
733        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
734        enforceCallerCanReadSessions();
735
736        mCallbacks.register(callback, userId);
737    }
738
739    @Override
740    public void unregisterCallback(IPackageInstallerCallback callback) {
741        mCallbacks.unregister(callback);
742    }
743
744    private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
745            int installerUid) {
746        int count = 0;
747        final int size = sessions.size();
748        for (int i = 0; i < size; i++) {
749            final PackageInstallerSession session = sessions.valueAt(i);
750            if (session.installerUid == installerUid) {
751                count++;
752            }
753        }
754        return count;
755    }
756
757    private boolean isCallingUidOwner(PackageInstallerSession session) {
758        final int callingUid = Binder.getCallingUid();
759        if (callingUid == Process.ROOT_UID) {
760            return true;
761        } else {
762            return (session != null) && (callingUid == session.installerUid);
763        }
764    }
765
766    /**
767     * We allow those with permission, or the current home app.
768     */
769    private void enforceCallerCanReadSessions() {
770        final boolean hasPermission = (mContext.checkCallingOrSelfPermission(
771                android.Manifest.permission.READ_INSTALL_SESSIONS)
772                == PackageManager.PERMISSION_GRANTED);
773        final boolean isHomeApp = mPm.checkCallerIsHomeApp();
774        if (hasPermission || isHomeApp) {
775            return;
776        } else {
777            throw new SecurityException("Caller must be current home app to read install sessions");
778        }
779    }
780
781    static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
782        private final Context mContext;
783        private final IntentSender mTarget;
784        private final String mPackageName;
785
786        public PackageDeleteObserverAdapter(Context context, IntentSender target,
787                String packageName) {
788            mContext = context;
789            mTarget = target;
790            mPackageName = packageName;
791        }
792
793        @Override
794        public void onUserActionRequired(Intent intent) {
795            final Intent fillIn = new Intent();
796            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
797            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
798                    PackageInstaller.STATUS_PENDING_USER_ACTION);
799            fillIn.putExtra(Intent.EXTRA_INTENT, intent);
800            try {
801                mTarget.sendIntent(mContext, 0, fillIn, null, null);
802            } catch (SendIntentException ignored) {
803            }
804        }
805
806        @Override
807        public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
808            final Intent fillIn = new Intent();
809            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
810            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
811                    PackageManager.deleteStatusToPublicStatus(returnCode));
812            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
813                    PackageManager.deleteStatusToString(returnCode, msg));
814            fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
815            try {
816                mTarget.sendIntent(mContext, 0, fillIn, null, null);
817            } catch (SendIntentException ignored) {
818            }
819        }
820    }
821
822    static class PackageInstallObserverAdapter extends PackageInstallObserver {
823        private final Context mContext;
824        private final IntentSender mTarget;
825        private final int mSessionId;
826
827        public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId) {
828            mContext = context;
829            mTarget = target;
830            mSessionId = sessionId;
831        }
832
833        @Override
834        public void onUserActionRequired(Intent intent) {
835            final Intent fillIn = new Intent();
836            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
837            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
838                    PackageInstaller.STATUS_PENDING_USER_ACTION);
839            fillIn.putExtra(Intent.EXTRA_INTENT, intent);
840            try {
841                mTarget.sendIntent(mContext, 0, fillIn, null, null);
842            } catch (SendIntentException ignored) {
843            }
844        }
845
846        @Override
847        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
848                Bundle extras) {
849            final Intent fillIn = new Intent();
850            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
851            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
852                    PackageManager.installStatusToPublicStatus(returnCode));
853            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
854                    PackageManager.installStatusToString(returnCode, msg));
855            fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
856            if (extras != null) {
857                final String existing = extras.getString(
858                        PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
859                if (!TextUtils.isEmpty(existing)) {
860                    fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, existing);
861                }
862            }
863            try {
864                mTarget.sendIntent(mContext, 0, fillIn, null, null);
865            } catch (SendIntentException ignored) {
866            }
867        }
868    }
869
870    private static class Callbacks extends Handler {
871        private static final int MSG_SESSION_CREATED = 1;
872        private static final int MSG_SESSION_OPENED = 2;
873        private static final int MSG_SESSION_PROGRESS_CHANGED = 3;
874        private static final int MSG_SESSION_CLOSED = 4;
875        private static final int MSG_SESSION_FINISHED = 5;
876
877        private final RemoteCallbackList<IPackageInstallerCallback>
878                mCallbacks = new RemoteCallbackList<>();
879
880        public Callbacks(Looper looper) {
881            super(looper);
882        }
883
884        public void register(IPackageInstallerCallback callback, int userId) {
885            mCallbacks.register(callback, new UserHandle(userId));
886        }
887
888        public void unregister(IPackageInstallerCallback callback) {
889            mCallbacks.unregister(callback);
890        }
891
892        @Override
893        public void handleMessage(Message msg) {
894            final int userId = msg.arg2;
895            final int n = mCallbacks.beginBroadcast();
896            for (int i = 0; i < n; i++) {
897                final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
898                final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
899                // TODO: dispatch notifications for slave profiles
900                if (userId == user.getIdentifier()) {
901                    try {
902                        invokeCallback(callback, msg);
903                    } catch (RemoteException ignored) {
904                    }
905                }
906            }
907            mCallbacks.finishBroadcast();
908        }
909
910        private void invokeCallback(IPackageInstallerCallback callback, Message msg)
911                throws RemoteException {
912            final int sessionId = msg.arg1;
913            switch (msg.what) {
914                case MSG_SESSION_CREATED:
915                    callback.onSessionCreated(sessionId);
916                    break;
917                case MSG_SESSION_OPENED:
918                    callback.onSessionOpened(sessionId);
919                    break;
920                case MSG_SESSION_PROGRESS_CHANGED:
921                    callback.onSessionProgressChanged(sessionId, (float) msg.obj);
922                    break;
923                case MSG_SESSION_CLOSED:
924                    callback.onSessionClosed(sessionId);
925                    break;
926                case MSG_SESSION_FINISHED:
927                    callback.onSessionFinished(sessionId, (boolean) msg.obj);
928                    break;
929            }
930        }
931
932        private void notifySessionCreated(int sessionId, int userId) {
933            obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
934        }
935
936        private void notifySessionOpened(int sessionId, int userId) {
937            obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget();
938        }
939
940        private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
941            obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
942        }
943
944        private void notifySessionClosed(int sessionId, int userId) {
945            obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget();
946        }
947
948        public void notifySessionFinished(int sessionId, int userId, boolean success) {
949            obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
950        }
951    }
952
953    void dump(IndentingPrintWriter pw) {
954        synchronized (mSessions) {
955            pw.println("Active install sessions:");
956            pw.increaseIndent();
957            int N = mSessions.size();
958            for (int i = 0; i < N; i++) {
959                final PackageInstallerSession session = mSessions.valueAt(i);
960                session.dump(pw);
961                pw.println();
962            }
963            pw.println();
964            pw.decreaseIndent();
965
966            pw.println("Historical install sessions:");
967            pw.increaseIndent();
968            N = mHistoricalSessions.size();
969            for (int i = 0; i < N; i++) {
970                final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
971                session.dump(pw);
972                pw.println();
973            }
974            pw.println();
975            pw.decreaseIndent();
976
977            pw.println("Legacy install sessions:");
978            pw.increaseIndent();
979            pw.println(mLegacySessions.toString());
980            pw.decreaseIndent();
981        }
982    }
983
984    class InternalCallback {
985        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
986            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
987        }
988
989        public void onSessionOpened(PackageInstallerSession session) {
990            mCallbacks.notifySessionOpened(session.sessionId, session.userId);
991        }
992
993        public void onSessionClosed(PackageInstallerSession session) {
994            mCallbacks.notifySessionClosed(session.sessionId, session.userId);
995        }
996
997        public void onSessionFinished(PackageInstallerSession session, boolean success) {
998            mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
999            synchronized (mSessions) {
1000                mSessions.remove(session.sessionId);
1001                mHistoricalSessions.put(session.sessionId, session);
1002            }
1003            writeSessionsAsync();
1004        }
1005
1006        public void onSessionSealed(PackageInstallerSession session) {
1007            // It's very important that we block until we've recorded the
1008            // session as being sealed, since we never want to allow mutation
1009            // after sealing.
1010            writeSessionsLocked();
1011        }
1012    }
1013}
1014