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