1 /*
2  * Copyright (C) 2023 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 android.app.ActivityManager.START_ABORTED;
20 import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
21 import static android.app.ActivityManager.START_PERMISSION_DENIED;
22 import static android.app.AppOpsManager.MODE_ALLOWED;
23 import static android.app.AppOpsManager.MODE_IGNORED;
24 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
25 import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
26 import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
27 import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
28 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
29 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
30 import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
31 import static android.content.pm.PackageManager.DELETE_ALL_USERS;
32 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
33 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
34 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
35 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
36 import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
37 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
38 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
39 
40 import android.Manifest;
41 import android.annotation.NonNull;
42 import android.annotation.Nullable;
43 import android.annotation.RequiresPermission;
44 import android.annotation.UserIdInt;
45 import android.app.ActivityManager;
46 import android.app.AppOpsManager;
47 import android.app.BroadcastOptions;
48 import android.app.PendingIntent;
49 import android.content.ComponentName;
50 import android.content.Context;
51 import android.content.IIntentReceiver;
52 import android.content.IIntentSender;
53 import android.content.Intent;
54 import android.content.IntentSender;
55 import android.content.pm.ApplicationInfo;
56 import android.content.pm.ArchivedActivityParcel;
57 import android.content.pm.ArchivedPackageInfo;
58 import android.content.pm.ArchivedPackageParcel;
59 import android.content.pm.Flags;
60 import android.content.pm.LauncherActivityInfo;
61 import android.content.pm.LauncherApps;
62 import android.content.pm.PackageInstaller;
63 import android.content.pm.PackageManager;
64 import android.content.pm.ParceledListSlice;
65 import android.content.pm.ResolveInfo;
66 import android.content.pm.UserInfo;
67 import android.content.pm.VersionedPackage;
68 import android.graphics.Bitmap;
69 import android.graphics.BitmapFactory;
70 import android.graphics.Color;
71 import android.graphics.PorterDuff;
72 import android.graphics.PorterDuffColorFilter;
73 import android.graphics.drawable.AdaptiveIconDrawable;
74 import android.graphics.drawable.BitmapDrawable;
75 import android.graphics.drawable.ColorDrawable;
76 import android.graphics.drawable.Drawable;
77 import android.graphics.drawable.InsetDrawable;
78 import android.graphics.drawable.LayerDrawable;
79 import android.os.Binder;
80 import android.os.Bundle;
81 import android.os.Environment;
82 import android.os.FileUtils;
83 import android.os.IBinder;
84 import android.os.ParcelableException;
85 import android.os.Process;
86 import android.os.RemoteException;
87 import android.os.SELinux;
88 import android.os.UserHandle;
89 import android.os.UserManager;
90 import android.text.TextUtils;
91 import android.util.ExceptionUtils;
92 import android.util.Pair;
93 import android.util.Slog;
94 import android.util.SparseArray;
95 
96 import com.android.internal.R;
97 import com.android.internal.annotations.GuardedBy;
98 import com.android.internal.annotations.VisibleForTesting;
99 import com.android.server.pm.pkg.ArchiveState;
100 import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
101 import com.android.server.pm.pkg.PackageState;
102 import com.android.server.pm.pkg.PackageStateInternal;
103 import com.android.server.pm.pkg.PackageUserState;
104 import com.android.server.pm.pkg.PackageUserStateInternal;
105 
106 import java.io.File;
107 import java.io.FileOutputStream;
108 import java.io.IOException;
109 import java.nio.file.Path;
110 import java.util.ArrayList;
111 import java.util.HashMap;
112 import java.util.List;
113 import java.util.Map;
114 import java.util.Objects;
115 import java.util.Set;
116 import java.util.concurrent.CompletableFuture;
117 
118 /**
119  * Responsible archiving apps and returning information about archived apps.
120  *
121  * <p> An archived app is in a state where the app is not fully on the device. APKs are removed
122  * while the data directory is kept. Archived apps are included in the list of launcher apps where
123  * tapping them re-installs the full app.
124  */
125 public class PackageArchiver {
126 
127     private static final String TAG = "PackageArchiverService";
128     private static final boolean DEBUG = true;
129 
130     public static final String EXTRA_UNARCHIVE_INTENT_SENDER =
131             "android.content.pm.extra.UNARCHIVE_INTENT_SENDER";
132 
133     /**
134      * The maximum time granted for an app store to start a foreground service when unarchival
135      * is requested.
136      */
137     // TODO(b/297358628) Make this configurable through a flag.
138     private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;
139 
140     private static final String ARCHIVE_ICONS_DIR = "package_archiver";
141 
142     private static final String ACTION_UNARCHIVE_DIALOG =
143             "com.android.intent.action.UNARCHIVE_DIALOG";
144     private static final String ACTION_UNARCHIVE_ERROR_DIALOG =
145             "com.android.intent.action.UNARCHIVE_ERROR_DIALOG";
146 
147     private static final String EXTRA_REQUIRED_BYTES =
148             "com.android.content.pm.extra.UNARCHIVE_EXTRA_REQUIRED_BYTES";
149     private static final String EXTRA_INSTALLER_PACKAGE_NAME =
150             "com.android.content.pm.extra.UNARCHIVE_INSTALLER_PACKAGE_NAME";
151     private static final String EXTRA_INSTALLER_TITLE =
152             "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";
153 
154     private static final PorterDuffColorFilter OPACITY_LAYER_FILTER =
155             new PorterDuffColorFilter(
156                     Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
157                     PorterDuff.Mode.SRC_ATOP);
158 
159     private final Context mContext;
160     private final PackageManagerService mPm;
161 
162     private final AppStateHelper mAppStateHelper;
163 
164     @Nullable
165     private LauncherApps mLauncherApps;
166 
167     @Nullable
168     private AppOpsManager mAppOpsManager;
169 
170     @Nullable
171     private UserManager mUserManager;
172 
173     /* IntentSender store that maps key: {userId, appPackageName} to respective existing attached
174      unarchival intent sender. */
175     private final Map<Pair<Integer, String>, IntentSender> mLauncherIntentSenders;
176 
PackageArchiver(Context context, PackageManagerService mPm)177     PackageArchiver(Context context, PackageManagerService mPm) {
178         this.mContext = context;
179         this.mPm = mPm;
180         this.mAppStateHelper = new AppStateHelper(mContext);
181         this.mLauncherIntentSenders = new HashMap<>();
182     }
183 
184     /** Returns whether a package is archived for a user. */
isArchived(PackageUserState userState)185     public static boolean isArchived(PackageUserState userState) {
186         return userState.getArchiveState() != null && !userState.isInstalled();
187     }
188 
isArchivingEnabled()189     public static boolean isArchivingEnabled() {
190         return Flags.archiving();
191     }
192 
193     @VisibleForTesting
requestArchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender intentSender, @NonNull UserHandle userHandle)194     void requestArchive(
195             @NonNull String packageName,
196             @NonNull String callerPackageName,
197             @NonNull IntentSender intentSender,
198             @NonNull UserHandle userHandle) {
199         requestArchive(packageName, callerPackageName, /*flags=*/ 0, intentSender, userHandle);
200     }
201 
requestArchive( @onNull String packageName, @NonNull String callerPackageName, int flags, @NonNull IntentSender intentSender, @NonNull UserHandle userHandle)202     void requestArchive(
203             @NonNull String packageName,
204             @NonNull String callerPackageName,
205             int flags,
206             @NonNull IntentSender intentSender,
207             @NonNull UserHandle userHandle) {
208         Objects.requireNonNull(packageName);
209         Objects.requireNonNull(callerPackageName);
210         Objects.requireNonNull(intentSender);
211         Objects.requireNonNull(userHandle);
212 
213         Slog.i(TAG,
214                 TextUtils.formatSimple("Requested archival of package %s for user %s.", packageName,
215                         userHandle.getIdentifier()));
216         Computer snapshot = mPm.snapshotComputer();
217         int binderUserId = userHandle.getIdentifier();
218         int binderUid = Binder.getCallingUid();
219         int binderPid = Binder.getCallingPid();
220         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
221             verifyCaller(snapshot.getPackageUid(callerPackageName, 0, binderUserId), binderUid);
222         }
223 
224         final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
225         final int[] users = deleteAllUsers ? mPm.mInjector.getUserManagerInternal().getUserIds()
226                 : new int[]{binderUserId};
227         for (int userId : users) {
228             snapshot.enforceCrossUserPermission(binderUid, userId,
229                     /*requireFullPermission=*/ true, /*checkShell=*/ true,
230                     "archiveApp");
231         }
232         verifyUninstallPermissions();
233 
234         CompletableFuture<Void>[] archiveStateStored = new CompletableFuture[users.length];
235         try {
236             for (int i = 0, size = users.length; i < size; ++i) {
237                 archiveStateStored[i] = createAndStoreArchiveState(packageName, users[i]);
238             }
239         } catch (PackageManager.NameNotFoundException e) {
240             Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
241                     packageName, e.getMessage()));
242             throw new ParcelableException(e);
243         }
244 
245         final int deleteFlags = DELETE_ARCHIVE | DELETE_KEEP_DATA
246                 | (deleteAllUsers ? DELETE_ALL_USERS : 0);
247 
248         CompletableFuture.allOf(archiveStateStored).thenAccept(ignored ->
249                 mPm.mInstallerService.uninstall(
250                         new VersionedPackage(packageName,
251                                 PackageManager.VERSION_CODE_HIGHEST),
252                         callerPackageName,
253                         deleteFlags,
254                         intentSender,
255                         binderUserId,
256                         binderUid,
257                         binderPid)
258         ).exceptionally(
259                 e -> {
260                     Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
261                             packageName, e.getMessage()));
262                     sendFailureStatus(intentSender, packageName, e.getMessage());
263                     return null;
264                 }
265         );
266     }
267 
268     /**
269      * Starts unarchival for the package corresponding to the startActivity intent. Note that this
270      * will work only if the caller is the default/Home Launcher or if activity is started via Shell
271      * identity.
272      */
273     @NonNull
requestUnarchiveOnActivityStart(@ullable Intent intent, @Nullable String callerPackageName, int userId, int callingUid)274     public int requestUnarchiveOnActivityStart(@Nullable Intent intent,
275             @Nullable String callerPackageName, int userId, int callingUid) {
276         String packageName = getPackageNameFromIntent(intent);
277         if (packageName == null) {
278             Slog.e(TAG, "packageName cannot be null for unarchival!");
279             return START_CLASS_NOT_FOUND;
280         }
281         if (callerPackageName == null) {
282             Slog.e(TAG, "callerPackageName cannot be null for unarchival!");
283             return START_CLASS_NOT_FOUND;
284         }
285 
286         String currentLauncherPackageName = getCurrentLauncherPackageName(getParentUserId(userId));
287         if ((currentLauncherPackageName == null || !TextUtils.equals(callerPackageName,
288                 currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
289             // TODO(b/311619990): Remove dependency on SHELL_UID for testing
290             Slog.e(TAG, TextUtils.formatSimple(
291                     "callerPackageName: %s does not qualify for unarchival of package: " + "%s!",
292                     callerPackageName, packageName));
293             return START_PERMISSION_DENIED;
294         }
295 
296         try {
297             boolean openAppDetailsIfOngoingUnarchival = getAppOpsManager().checkOp(
298                     AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
299                     == MODE_ALLOWED;
300             if (openAppDetailsIfOngoingUnarchival) {
301                 PackageInstaller.SessionInfo activeUnarchivalSession = getActiveUnarchivalSession(
302                         packageName, userId);
303                 if (activeUnarchivalSession != null) {
304                     mPm.mHandler.post(() -> {
305                         Slog.i(TAG, "Opening app details page for ongoing unarchival of: "
306                                 + packageName);
307                         getLauncherApps().startPackageInstallerSessionDetailsActivity(
308                                 activeUnarchivalSession, null, null);
309                     });
310                     return START_ABORTED;
311                 }
312             }
313 
314             Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
315 
316             requestUnarchive(packageName, callerPackageName,
317                     getOrCreateLauncherListener(userId, packageName),
318                     UserHandle.of(userId),
319                     getAppOpsManager().checkOp(
320                             AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
321                             == MODE_ALLOWED);
322         } catch (Throwable t) {
323             Slog.e(TAG, TextUtils.formatSimple(
324                     "Unexpected error occurred while unarchiving package %s: %s.", packageName,
325                     t.getLocalizedMessage()));
326         }
327 
328         // We return STATUS_ABORTED because:
329         // 1. Archived App is not actually present during activity start. Hence the unarchival
330         // start should be treated as an error code.
331         // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
332         // experience.
333         // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
334         // aborting activity options, animations etc in the Windows Manager.
335         return START_ABORTED;
336     }
337 
338     // Profiles share their UI and default apps, so we have to get the profile parent before
339     // fetching the default launcher.
getParentUserId(int userId)340     private int getParentUserId(int userId) {
341         UserInfo profileParent = getUserManager().getProfileParent(userId);
342         return profileParent == null ? userId : profileParent.id;
343     }
344 
345     /**
346      * Returns true if the componentName targeted by the intent corresponds to that of an archived
347      * app.
348      */
isIntentResolvedToArchivedApp(Intent intent, int userId)349     public boolean isIntentResolvedToArchivedApp(Intent intent, int userId) {
350         String packageName = getPackageNameFromIntent(intent);
351         if (packageName == null || intent.getComponent() == null) {
352             return false;
353         }
354         PackageState packageState = mPm.snapshotComputer().getPackageStateInternal(packageName);
355         if (packageState == null) {
356             return false;
357         }
358         PackageUserState userState = packageState.getUserStateOrDefault(userId);
359         if (!PackageArchiver.isArchived(userState)) {
360             return false;
361         }
362         List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList =
363                 userState.getArchiveState().getActivityInfos();
364         for (int i = 0; i < archiveActivityInfoList.size(); i++) {
365             if (archiveActivityInfoList.get(i)
366                     .getOriginalComponentName().equals(intent.getComponent())) {
367                 return true;
368             }
369         }
370         Slog.e(TAG, TextUtils.formatSimple(
371                 "Package: %s is archived but component to start main activity"
372                         + " cannot be found!", packageName));
373         return false;
374     }
375 
clearArchiveState(String packageName, int userId)376     void clearArchiveState(String packageName, int userId) {
377         final PackageSetting ps;
378         synchronized (mPm.mLock) {
379             ps = mPm.mSettings.getPackageLPr(packageName);
380         }
381         clearArchiveState(ps, userId);
382     }
383 
clearArchiveState(PackageSetting ps, int userId)384     void clearArchiveState(PackageSetting ps, int userId) {
385         synchronized (mPm.mLock) {
386             if (ps == null || ps.getUserStateOrDefault(userId).getArchiveState() == null) {
387                 // No archive states to clear
388                 return;
389             }
390             if (DEBUG) {
391                 Slog.e(TAG, "Clearing archive states for " + ps.getPackageName());
392             }
393             ps.setArchiveState(/* archiveState= */ null, userId);
394         }
395         File iconsDir = getIconsDir(ps.getPackageName(), userId);
396         if (!iconsDir.exists()) {
397             if (DEBUG) {
398                 Slog.e(TAG, "Icons are already deleted at " + iconsDir.getAbsolutePath());
399             }
400             return;
401         }
402         // TODO(b/319238030) Move this into installd.
403         if (!FileUtils.deleteContentsAndDir(iconsDir)) {
404             Slog.e(TAG, "Failed to clean up archive files for " + ps.getPackageName());
405         } else {
406             if (DEBUG) {
407                 Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
408             }
409         }
410     }
411 
412     @Nullable
getCurrentLauncherPackageName(int userId)413     private String getCurrentLauncherPackageName(int userId) {
414         ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity(
415                 userId);
416         if (defaultLauncherComponent != null) {
417             return defaultLauncherComponent.getPackageName();
418         }
419         return null;
420     }
421 
isCallingPackageValid(String callingPackage, int callingUid, int userId)422     private boolean isCallingPackageValid(String callingPackage, int callingUid, int userId) {
423         int packageUid;
424         packageUid = mPm.snapshotComputer().getPackageUid(callingPackage, 0L, userId);
425         if (packageUid != callingUid) {
426             Slog.w(TAG, TextUtils.formatSimple("Calling package: %s does not belong to uid: %d",
427                     callingPackage, callingUid));
428             return false;
429         }
430         return true;
431     }
432 
getOrCreateLauncherListener(int userId, String packageName)433     private IntentSender getOrCreateLauncherListener(int userId, String packageName) {
434         Pair<Integer, String> key = Pair.create(userId, packageName);
435         synchronized (mLauncherIntentSenders) {
436             IntentSender intentSender = mLauncherIntentSenders.get(key);
437             if (intentSender != null) {
438                 return intentSender;
439             }
440             IntentSender unarchiveIntentSender = new IntentSender(
441                     (IIntentSender) new UnarchiveIntentSender());
442             mLauncherIntentSenders.put(key, unarchiveIntentSender);
443             return unarchiveIntentSender;
444         }
445     }
446 
447     /** Creates archived state for the package and user. */
createAndStoreArchiveState(String packageName, int userId)448     private CompletableFuture<Void> createAndStoreArchiveState(String packageName, int userId)
449             throws PackageManager.NameNotFoundException {
450         Computer snapshot = mPm.snapshotComputer();
451         PackageStateInternal ps = getPackageState(packageName, snapshot,
452                 Binder.getCallingUid(), userId);
453         verifyNotSystemApp(ps.getFlags());
454         verifyInstalled(ps, userId);
455         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
456         ApplicationInfo installerInfo = verifyInstaller(
457                 snapshot, responsibleInstallerPackage, userId);
458         verifyOptOutStatus(packageName,
459                 UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId())));
460 
461         List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
462                 userId);
463         final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>();
464         mPm.mHandler.post(() -> {
465             try {
466                 final String installerTitle = getResponsibleInstallerTitle(
467                         mContext, installerInfo, responsibleInstallerPackage, userId);
468                 var archiveState = createArchiveStateInternal(packageName, userId, mainActivities,
469                         installerTitle);
470                 storeArchiveState(packageName, archiveState, userId);
471                 archiveStateStored.complete(null);
472             } catch (IOException | PackageManager.NameNotFoundException e) {
473                 archiveStateStored.completeExceptionally(e);
474             }
475         });
476         return archiveStateStored;
477     }
478 
479     @Nullable
createArchiveState(@onNull ArchivedPackageParcel archivedPackage, int userId, String installerPackage, String responsibleInstallerTitle)480     ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
481             int userId, String installerPackage, String responsibleInstallerTitle) {
482         ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo(
483                 installerPackage, /* flags= */ 0, userId);
484         if (installerInfo == null) {
485             // Should never happen because we just fetched the installerInfo.
486             Slog.e(TAG, "Couldn't find installer " + installerPackage);
487             return null;
488         }
489         if (responsibleInstallerTitle == null) {
490             Slog.e(TAG, "Couldn't get the title of the installer");
491             return null;
492         }
493 
494         final int iconSize = mContext.getSystemService(
495                 ActivityManager.class).getLauncherLargeIconSize();
496 
497         var info = new ArchivedPackageInfo(archivedPackage);
498 
499         try {
500             var packageName = info.getPackageName();
501             var mainActivities = info.getLauncherActivities();
502             List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
503             for (int i = 0, size = mainActivities.size(); i < size; ++i) {
504                 var mainActivity = mainActivities.get(i);
505                 Path iconPath = storeAdaptiveDrawable(
506                         packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize);
507                 Path monochromePath = storeAdaptiveDrawable(
508                         packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize);
509                 ArchiveActivityInfo activityInfo =
510                         new ArchiveActivityInfo(
511                                 mainActivity.getLabel().toString(),
512                                 mainActivity.getComponentName(),
513                                 iconPath,
514                                 monochromePath);
515                 archiveActivityInfos.add(activityInfo);
516             }
517 
518             return new ArchiveState(archiveActivityInfos, responsibleInstallerTitle);
519         } catch (IOException e) {
520             Slog.e(TAG, "Failed to create archive state", e);
521             return null;
522         }
523     }
524 
createArchiveStateInternal(String packageName, int userId, List<LauncherActivityInfo> mainActivities, String installerTitle)525     ArchiveState createArchiveStateInternal(String packageName, int userId,
526             List<LauncherActivityInfo> mainActivities, String installerTitle)
527             throws IOException {
528         final int iconSize = mContext.getSystemService(
529                 ActivityManager.class).getLauncherLargeIconSize();
530 
531         List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
532         for (int i = 0, size = mainActivities.size(); i < size; i++) {
533             LauncherActivityInfo mainActivity = mainActivities.get(i);
534             Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize);
535             // i * 2 + 1 reserved for monochromeIcon
536             ArchiveActivityInfo activityInfo =
537                     new ArchiveActivityInfo(
538                             mainActivity.getLabel().toString(),
539                             mainActivity.getComponentName(),
540                             iconPath,
541                             null);
542             archiveActivityInfos.add(activityInfo);
543         }
544 
545         return new ArchiveState(archiveActivityInfos, installerTitle);
546     }
547 
548     @VisibleForTesting
storeIcon(String packageName, LauncherActivityInfo mainActivity, @UserIdInt int userId, int index, int iconSize)549     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
550             @UserIdInt int userId, int index, int iconSize) throws IOException {
551         int iconResourceId = mainActivity.getActivityInfo().getIconResource();
552         if (iconResourceId == 0) {
553             // The app doesn't define an icon. No need to store anything.
554             return null;
555         }
556         return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index,
557                 iconSize);
558     }
559 
storeDrawable(String packageName, @Nullable Drawable iconDrawable, @UserIdInt int userId, int index, int iconSize)560     private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable,
561             @UserIdInt int userId, int index, int iconSize) throws IOException {
562         if (iconDrawable == null) {
563             return null;
564         }
565         File iconsDir = createIconsDir(packageName, userId);
566         File iconFile = new File(iconsDir, index + ".png");
567         Bitmap icon = drawableToBitmap(iconDrawable, iconSize);
568         try (FileOutputStream out = new FileOutputStream(iconFile)) {
569             // Note: Quality is ignored for PNGs.
570             if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
571                 throw new IOException(TextUtils.formatSimple("Failure to store icon file %s",
572                         iconFile.getAbsolutePath()));
573             }
574             out.flush();
575         }
576         if (DEBUG && iconFile.exists()) {
577             Slog.i(TAG, "Stored icon at " + iconFile.getAbsolutePath());
578         }
579         return iconFile.toPath();
580     }
581 
582     /**
583      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
584      * This allows the badging to be done based on the actual bitmap size rather than
585      * the scaled bitmap size.
586      */
587     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
588 
FixedSizeBitmapDrawable(@ullable final Bitmap bitmap)589         FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) {
590             super(null, bitmap);
591         }
592 
593         @Override
getIntrinsicHeight()594         public int getIntrinsicHeight() {
595             return getBitmap().getWidth();
596         }
597 
598         @Override
getIntrinsicWidth()599         public int getIntrinsicWidth() {
600             return getBitmap().getWidth();
601         }
602     }
603 
604     /**
605      * Create an <a
606      * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
607      * adaptive icon</a> from an icon.
608      * This is necessary so the icon can be displayed properly by different launchers.
609      */
storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable, @UserIdInt int userId, int index, int iconSize)610     private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable,
611             @UserIdInt int userId, int index, int iconSize) throws IOException {
612         if (iconDrawable == null) {
613             return null;
614         }
615 
616         // see BaseIconFactory#createShapedIconBitmap
617         if (iconDrawable instanceof BitmapDrawable) {
618             var icon = ((BitmapDrawable) iconDrawable).getBitmap();
619             iconDrawable = new FixedSizeBitmapDrawable(icon);
620         }
621 
622         float inset = getExtraInsetFraction();
623         inset = inset / (1 + 2 * inset);
624         Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
625                 new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset));
626 
627         return storeDrawable(packageName, d, userId, index, iconSize);
628     }
629 
630 
verifyInstaller(Computer snapshot, String installerPackageName, int userId)631     private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName,
632             int userId) throws PackageManager.NameNotFoundException {
633         if (TextUtils.isEmpty(installerPackageName)) {
634             throw new PackageManager.NameNotFoundException("No installer found");
635         }
636         // Allow shell for easier development.
637         if ((Binder.getCallingUid() != Process.SHELL_UID)
638                 && !verifySupportsUnarchival(installerPackageName, userId)) {
639             throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
640         }
641         ApplicationInfo appInfo = snapshot.getApplicationInfo(
642                 installerPackageName, /* flags=*/ 0, userId);
643         if (appInfo == null) {
644             throw new PackageManager.NameNotFoundException("Failed to obtain Installer info");
645         }
646         return appInfo;
647     }
648 
649     /**
650      * Returns true if {@code installerPackage} supports unarchival being able to handle
651      * {@link Intent#ACTION_UNARCHIVE_PACKAGE}
652      */
verifySupportsUnarchival(String installerPackage, int userId)653     public boolean verifySupportsUnarchival(String installerPackage, int userId) {
654         if (TextUtils.isEmpty(installerPackage)) {
655             return false;
656         }
657 
658         Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage);
659 
660         ParceledListSlice<ResolveInfo> intentReceivers =
661                 Binder.withCleanCallingIdentity(
662                         () -> mPm.queryIntentReceivers(mPm.snapshotComputer(),
663                                 intent, /* resolvedType= */ null, /* flags= */ 0, userId));
664         return intentReceivers != null && !intentReceivers.getList().isEmpty();
665     }
666 
verifyNotSystemApp(int flags)667     private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException {
668         if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (
669                 (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
670             throw new PackageManager.NameNotFoundException("System apps cannot be archived.");
671         }
672     }
673 
verifyInstalled(PackageStateInternal ps, int userId)674     private void verifyInstalled(PackageStateInternal ps, int userId)
675             throws PackageManager.NameNotFoundException {
676         if (!ps.getUserStateOrDefault(userId).isInstalled()) {
677             throw new PackageManager.NameNotFoundException(
678                     TextUtils.formatSimple("%s is not installed.", ps.getPackageName()));
679         }
680     }
681 
682     /**
683      * Returns true if the app is archivable.
684      */
isAppArchivable(@onNull String packageName, @NonNull UserHandle user)685     public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) {
686         Objects.requireNonNull(packageName);
687         Objects.requireNonNull(user);
688 
689         Computer snapshot = mPm.snapshotComputer();
690         int userId = user.getIdentifier();
691         int binderUid = Binder.getCallingUid();
692         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
693                 "isAppArchivable");
694         PackageStateInternal ps;
695         try {
696             ps = getPackageState(packageName, mPm.snapshotComputer(),
697                     Binder.getCallingUid(), userId);
698         } catch (PackageManager.NameNotFoundException e) {
699             throw new ParcelableException(e);
700         }
701 
702         if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || (
703                 (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
704             return false;
705         }
706 
707         if (isAppOptedOutOfArchiving(packageName,
708                     UserHandle.getUid(userId, ps.getAppId()))) {
709             return false;
710         }
711 
712         try {
713             verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId);
714             getLauncherActivityInfos(packageName, userId);
715         } catch (PackageManager.NameNotFoundException e) {
716             return false;
717         }
718 
719         return true;
720     }
721 
722     /**
723      * Returns true if user has opted the app out of archiving through system settings.
724      */
isAppOptedOutOfArchiving(String packageName, int uid)725     private boolean isAppOptedOutOfArchiving(String packageName, int uid) {
726         return Binder.withCleanCallingIdentity(() ->
727                 getAppOpsManager().checkOpNoThrow(
728                         AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName)
729                         == MODE_IGNORED);
730     }
731 
verifyOptOutStatus(String packageName, int uid)732     private void verifyOptOutStatus(String packageName, int uid)
733             throws PackageManager.NameNotFoundException {
734         if (isAppOptedOutOfArchiving(packageName, uid)) {
735             throw new PackageManager.NameNotFoundException(
736                     TextUtils.formatSimple("The app %s is opted out of archiving.", packageName));
737         }
738     }
739 
requestUnarchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle)740     void requestUnarchive(
741             @NonNull String packageName,
742             @NonNull String callerPackageName,
743             @NonNull IntentSender statusReceiver,
744             @NonNull UserHandle userHandle) {
745         requestUnarchive(packageName, callerPackageName, statusReceiver, userHandle,
746                 false /* showUnarchivalConfirmation= */);
747     }
748 
requestUnarchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation)749     private void requestUnarchive(
750             @NonNull String packageName,
751             @NonNull String callerPackageName,
752             @NonNull IntentSender statusReceiver,
753             @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation) {
754         Objects.requireNonNull(packageName);
755         Objects.requireNonNull(callerPackageName);
756         Objects.requireNonNull(statusReceiver);
757         Objects.requireNonNull(userHandle);
758 
759         Computer snapshot = mPm.snapshotComputer();
760         int userId = userHandle.getIdentifier();
761         int binderUid = Binder.getCallingUid();
762         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
763             verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
764         }
765         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
766                 "unarchiveApp");
767 
768         PackageStateInternal ps;
769         PackageStateInternal callerPs;
770         try {
771             ps = getPackageState(packageName, snapshot, binderUid, userId);
772             callerPs = getPackageState(callerPackageName, snapshot, binderUid, userId);
773             verifyArchived(ps, userId);
774         } catch (PackageManager.NameNotFoundException e) {
775             throw new ParcelableException(e);
776         }
777         String installerPackage = getResponsibleInstallerPackage(ps);
778         if (installerPackage == null) {
779             throw new ParcelableException(
780                     new PackageManager.NameNotFoundException(
781                             TextUtils.formatSimple("No installer found to unarchive app %s.",
782                                     packageName)));
783         }
784 
785         boolean hasInstallPackages = mContext.checkCallingOrSelfPermission(
786                 Manifest.permission.INSTALL_PACKAGES)
787                 == PackageManager.PERMISSION_GRANTED;
788         // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester
789         // is not the source of the installation.
790         boolean hasRequestInstallPackages = callerPs.getAndroidPackage().getRequestedPermissions()
791                 .contains(android.Manifest.permission.REQUEST_INSTALL_PACKAGES);
792         if (!hasInstallPackages && !hasRequestInstallPackages) {
793             throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
794                     + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
795                     + "an unarchival.");
796         }
797 
798         if (!hasInstallPackages || showUnarchivalConfirmation) {
799             requestUnarchiveConfirmation(packageName, statusReceiver, userHandle);
800             return;
801         }
802 
803         // TODO(b/311709794) Check that the responsible installer has INSTALL_PACKAGES or
804         // OPSTR_REQUEST_INSTALL_PACKAGES too. Edge case: In reality this should always be the case,
805         // unless a user has disabled the permission after archiving an app.
806 
807         int draftSessionId;
808         try {
809             draftSessionId = Binder.withCleanCallingIdentity(
810                     () -> createDraftSession(packageName, installerPackage, callerPackageName,
811                             statusReceiver, userId));
812         } catch (RuntimeException e) {
813             if (e.getCause() instanceof IOException) {
814                 throw ExceptionUtils.wrap((IOException) e.getCause());
815             } else {
816                 throw e;
817             }
818         }
819 
820         mPm.mHandler.post(() -> {
821             Slog.i(TAG, "Starting app unarchival for: " + packageName);
822             unarchiveInternal(packageName, userHandle, installerPackage,
823                     draftSessionId);
824         });
825     }
826 
827     @Nullable
getActiveUnarchivalSession(String packageName, int userId)828     private PackageInstaller.SessionInfo getActiveUnarchivalSession(String packageName,
829             int userId) {
830         List<PackageInstaller.SessionInfo> activeSessions =
831                 mPm.mInstallerService.getAllSessions(userId).getList();
832         for (int idx = 0; idx < activeSessions.size(); idx++) {
833             PackageInstaller.SessionInfo activeSession = activeSessions.get(idx);
834             if (TextUtils.equals(activeSession.appPackageName, packageName)
835                     && activeSession.userId == userId && activeSession.active
836                     && activeSession.isUnarchival()) {
837                 return activeSession;
838             }
839         }
840         return null;
841     }
842 
requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver, UserHandle user)843     private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver,
844             UserHandle user) {
845         final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG);
846         dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver);
847         dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
848 
849         final Intent broadcastIntent = new Intent();
850         broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
851         broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS,
852                 PackageInstaller.STATUS_PENDING_USER_ACTION);
853         broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
854         broadcastIntent.putExtra(Intent.EXTRA_USER, user);
855         sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
856     }
857 
verifyUninstallPermissions()858     private void verifyUninstallPermissions() {
859         if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
860                 != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
861                 Manifest.permission.REQUEST_DELETE_PACKAGES)
862                 != PackageManager.PERMISSION_GRANTED) {
863             throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
864                     + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
865                     + "an archival.");
866         }
867     }
868 
createDraftSession(String packageName, String installerPackage, String callerPackageName, IntentSender statusReceiver, int userId)869     private int createDraftSession(String packageName, String installerPackage,
870             String callerPackageName,
871             IntentSender statusReceiver, int userId) throws IOException {
872         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
873                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
874         sessionParams.setAppPackageName(packageName);
875         sessionParams.setAppLabel(
876                 mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
877         sessionParams.setAppIcon(
878                 getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
879         // To make sure SessionInfo::isUnarchival returns true for draft sessions,
880         // INSTALL_UNARCHIVE is also set.
881         sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
882 
883         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
884         // Handles case of repeated unarchival calls for the same package.
885         int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
886                 sessionParams,
887                 userId);
888         if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) {
889             attachListenerToSession(statusReceiver, existingSessionId, userId);
890             return existingSessionId;
891         }
892 
893         int sessionId = mPm.mInstallerService.createSessionInternal(
894                 sessionParams,
895                 installerPackage, mContext.getAttributionTag(),
896                 installerUid,
897                 userId);
898         attachListenerToSession(statusReceiver, sessionId, userId);
899 
900         // TODO(b/297358628) Also cleanup sessions upon device restart.
901         mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId),
902                 getUnarchiveForegroundTimeout());
903         return sessionId;
904     }
905 
attachListenerToSession(IntentSender statusReceiver, int existingSessionId, int userId)906     private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId,
907             int userId) {
908         PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId);
909         int status = session.getUnarchivalStatus();
910         // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK
911         // but hasn't created a session yet. Without this the listener would never receive a success
912         // response.
913         if (status == UNARCHIVAL_OK) {
914             notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(),
915                     session.params.appPackageName, /* requiredStorageBytes= */ 0,
916                     /* userActionIntent= */ null, Set.of(statusReceiver), userId);
917             return;
918         } else if (status != UNARCHIVAL_STATUS_UNSET) {
919             throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status"
920                     + "%s but is still active.", session.sessionId, status));
921         }
922 
923         session.registerUnarchivalListener(statusReceiver);
924     }
925 
926     /**
927      * Returns the icon of an archived app. This is the icon of the main activity of the app.
928      *
929      * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
930      * launcher activities, only one of the icons is returned arbitrarily.
931      */
932     @Nullable
getArchivedAppIcon(@onNull String packageName, @NonNull UserHandle user, String callingPackageName)933     public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
934             String callingPackageName) {
935         Objects.requireNonNull(packageName);
936         Objects.requireNonNull(user);
937 
938         Computer snapshot = mPm.snapshotComputer();
939         int callingUid = Binder.getCallingUid();
940         int userId = user.getIdentifier();
941         PackageStateInternal ps;
942         try {
943             ps = getPackageState(packageName, snapshot, callingUid, userId);
944         } catch (PackageManager.NameNotFoundException e) {
945             Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e);
946             return null;
947         }
948 
949         ArchiveState archiveState = getAnyArchiveState(ps, userId);
950         if (archiveState == null || archiveState.getActivityInfos().size() == 0) {
951             return null;
952         }
953 
954         // TODO(b/298452477) Handle monochrome icons.
955         // In the rare case the archived app defined more than two launcher activities, we choose
956         // the first one arbitrarily.
957         Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
958         if (icon != null && getAppOpsManager().checkOp(
959                 AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
960                 == MODE_ALLOWED) {
961             icon = includeCloudOverlay(icon);
962         }
963         return icon;
964     }
965 
966     /**
967      * This method first checks the ArchiveState for the provided userId and then tries to fallback
968      * to other users if the current user is not archived.
969      *
970      * <p> This fallback behaviour is required for archived apps to fit into the multi-user world
971      * where APKs are shared across users. E.g. current ways of fetching icons for apps that are
972      * only installed on the work profile also work when executed on the personal profile if you're
973      * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for
974      * the most part userId-agnostic, which we need to mimic here in order for existing methods
975      * like {@link PackageManager#getApplicationIcon} to continue working.
976      *
977      * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an
978      * arbitrary userId. If no user is archived, returns null.
979      */
980     @Nullable
getAnyArchiveState(PackageStateInternal ps, int userId)981     private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) {
982         PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
983         if (isArchived(userState)) {
984             return userState.getArchiveState();
985         }
986 
987         for (int i = 0; i < ps.getUserStates().size(); i++) {
988             userState = ps.getUserStates().valueAt(i);
989             if (isArchived(userState)) {
990                 return userState.getArchiveState();
991             }
992         }
993 
994         return null;
995     }
996 
997     @VisibleForTesting
998     @Nullable
decodeIcon(ArchiveActivityInfo activityInfo)999     Bitmap decodeIcon(ArchiveActivityInfo activityInfo) {
1000         Path iconBitmap = activityInfo.getIconBitmap();
1001         if (iconBitmap == null) {
1002             return null;
1003         }
1004         Bitmap bitmap = BitmapFactory.decodeFile(iconBitmap.toString());
1005         // TODO(b/278553670) We should throw here after some time. Failing graciously now because
1006         // we've just changed the place where we store icons.
1007         if (bitmap == null) {
1008             Slog.e(TAG, "Archived icon cannot be decoded " + iconBitmap.toAbsolutePath());
1009             return null;
1010         }
1011         return bitmap;
1012     }
1013 
1014     @Nullable
includeCloudOverlay(Bitmap bitmap)1015     Bitmap includeCloudOverlay(Bitmap bitmap) {
1016         Drawable cloudDrawable =
1017                 mContext.getResources()
1018                         .getDrawable(R.drawable.archived_app_cloud_overlay, mContext.getTheme());
1019         if (cloudDrawable == null) {
1020             Slog.e(TAG, "Unable to locate cloud overlay for archived app!");
1021             return bitmap;
1022         }
1023         BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
1024         appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER);
1025         appIconDrawable.setBounds(
1026                 0 /* left */,
1027                 0 /* top */,
1028                 cloudDrawable.getIntrinsicWidth(),
1029                 cloudDrawable.getIntrinsicHeight());
1030         LayerDrawable layerDrawable =
1031                 new LayerDrawable(new Drawable[]{appIconDrawable, cloudDrawable});
1032         final int iconSize = mContext.getSystemService(
1033                 ActivityManager.class).getLauncherLargeIconSize();
1034         Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize);
1035         if (bitmap != null) {
1036             bitmap.recycle();
1037         }
1038         return appIconWithCloudOverlay;
1039     }
1040 
verifyArchived(PackageStateInternal ps, int userId)1041     private void verifyArchived(PackageStateInternal ps, int userId)
1042             throws PackageManager.NameNotFoundException {
1043         PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
1044         if (!isArchived(userState)) {
1045             throw new PackageManager.NameNotFoundException(
1046                     TextUtils.formatSimple("Package %s is not currently archived.",
1047                             ps.getPackageName()));
1048         }
1049     }
1050 
1051     @RequiresPermission(
1052             allOf = {
1053                     Manifest.permission.INTERACT_ACROSS_USERS,
1054                     android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
1055                     android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
1056                     android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND},
1057             conditional = true)
unarchiveInternal(String packageName, UserHandle userHandle, String installerPackage, int unarchiveId)1058     private void unarchiveInternal(String packageName, UserHandle userHandle,
1059             String installerPackage, int unarchiveId) {
1060         int userId = userHandle.getIdentifier();
1061         Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
1062         unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1063         unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, unarchiveId);
1064         unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
1065         unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
1066                 userId == UserHandle.USER_ALL);
1067         unarchiveIntent.setPackage(installerPackage);
1068 
1069         // If the unarchival is requested for all users, the current user is used for unarchival.
1070         UserHandle userForUnarchival = userId == UserHandle.USER_ALL
1071                 ? UserHandle.of(mPm.mUserManager.getCurrentUserId())
1072                 : userHandle;
1073         mContext.sendOrderedBroadcastAsUser(
1074                 unarchiveIntent,
1075                 userForUnarchival,
1076                 /* receiverPermission = */ null,
1077                 AppOpsManager.OP_NONE,
1078                 createUnarchiveOptions(),
1079                 /* resultReceiver= */ null,
1080                 /* scheduler= */ null,
1081                 /* initialCode= */ 0,
1082                 /* initialData= */ null,
1083                 /* initialExtras= */ null);
1084     }
1085 
getLauncherActivityInfos(String packageName, int userId)1086     List<LauncherActivityInfo> getLauncherActivityInfos(String packageName,
1087             int userId) throws PackageManager.NameNotFoundException {
1088         List<LauncherActivityInfo> mainActivities =
1089                 Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
1090                         packageName,
1091                         new UserHandle(userId)));
1092         if (mainActivities.isEmpty()) {
1093             throw new PackageManager.NameNotFoundException(
1094                     TextUtils.formatSimple("The app %s does not have a main activity.",
1095                             packageName));
1096         }
1097 
1098         return mainActivities;
1099     }
1100 
1101     @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
1102             android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
1103             android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
createUnarchiveOptions()1104     private Bundle createUnarchiveOptions() {
1105         BroadcastOptions options = BroadcastOptions.makeBasic();
1106         options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(),
1107                 TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
1108                 REASON_PACKAGE_UNARCHIVE, "");
1109         return options.toBundle();
1110     }
1111 
getUnarchiveForegroundTimeout()1112     private static int getUnarchiveForegroundTimeout() {
1113         return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
1114     }
1115 
getResponsibleInstallerPackage(InstallSource installSource)1116     private static String getResponsibleInstallerPackage(InstallSource installSource) {
1117         return TextUtils.isEmpty(installSource.mUpdateOwnerPackageName)
1118                 ? installSource.mInstallerPackageName
1119                 : installSource.mUpdateOwnerPackageName;
1120     }
1121 
getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo, String responsibleInstallerPackage, int userId)1122     private static String getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo,
1123             String responsibleInstallerPackage, int userId)
1124             throws PackageManager.NameNotFoundException {
1125         final Context userContext = context.createPackageContextAsUser(
1126                 responsibleInstallerPackage, /* flags= */ 0, new UserHandle(userId));
1127         return appInfo.loadLabel(userContext.getPackageManager()).toString();
1128     }
1129 
getResponsibleInstallerPackage(PackageStateInternal ps)1130     static String getResponsibleInstallerPackage(PackageStateInternal ps) {
1131         return getResponsibleInstallerPackage(ps.getInstallSource());
1132     }
1133 
1134     @Nullable
getResponsibleInstallerTitles(Context context, Computer snapshot, InstallSource installSource, int requestUserId, int[] allUserIds)1135     static SparseArray<String> getResponsibleInstallerTitles(Context context, Computer snapshot,
1136             InstallSource installSource, int requestUserId, int[] allUserIds) {
1137         final String responsibleInstallerPackage = getResponsibleInstallerPackage(installSource);
1138         final SparseArray<String> responsibleInstallerTitles = new SparseArray<>();
1139         try {
1140             if (requestUserId != UserHandle.USER_ALL) {
1141                 final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
1142                         responsibleInstallerPackage, /* flags= */ 0, requestUserId);
1143                 if (responsibleInstallerInfo == null) {
1144                     return null;
1145                 }
1146 
1147                 final String title = getResponsibleInstallerTitle(context,
1148                         responsibleInstallerInfo, responsibleInstallerPackage, requestUserId);
1149                 responsibleInstallerTitles.put(requestUserId, title);
1150             } else {
1151                 // Go through all userIds.
1152                 for (int i = 0; i < allUserIds.length; i++) {
1153                     final int userId = allUserIds[i];
1154                     final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
1155                             responsibleInstallerPackage, /* flags= */ 0, userId);
1156                     // Can't get the applicationInfo on the user.
1157                     // Maybe the installer isn't installed on the user.
1158                     if (responsibleInstallerInfo == null) {
1159                         continue;
1160                     }
1161 
1162                     final String title = getResponsibleInstallerTitle(context,
1163                             responsibleInstallerInfo, responsibleInstallerPackage, userId);
1164                     responsibleInstallerTitles.put(userId, title);
1165                 }
1166             }
1167         } catch (PackageManager.NameNotFoundException ex) {
1168             return null;
1169         }
1170         return responsibleInstallerTitles;
1171     }
1172 
notifyUnarchivalListener(int status, String installerPackageName, String appPackageName, long requiredStorageBytes, @Nullable PendingIntent userActionIntent, Set<IntentSender> unarchiveIntentSenders, int userId)1173     void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
1174             long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
1175             Set<IntentSender> unarchiveIntentSenders, int userId) {
1176         final Intent broadcastIntent = new Intent();
1177         broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
1178         broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
1179 
1180         if (status != UNARCHIVAL_OK) {
1181             final Intent dialogIntent = createErrorDialogIntent(status, installerPackageName,
1182                     appPackageName,
1183                     requiredStorageBytes, userActionIntent, userId);
1184             if (dialogIntent == null) {
1185                 // Error already logged.
1186                 return;
1187             }
1188             broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
1189             broadcastIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
1190         }
1191 
1192         final BroadcastOptions options = BroadcastOptions.makeBasic();
1193         options.setPendingIntentBackgroundActivityStartMode(
1194                 MODE_BACKGROUND_ACTIVITY_START_DENIED);
1195         for (IntentSender intentSender : unarchiveIntentSenders) {
1196             try {
1197                 intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
1198                         /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
1199             } catch (IntentSender.SendIntentException e) {
1200                 Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
1201             } finally {
1202                 synchronized (mLauncherIntentSenders) {
1203                     mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
1204                 }
1205             }
1206         }
1207     }
1208 
1209     @Nullable
createErrorDialogIntent(int status, String installerPackageName, String appPackageName, long requiredStorageBytes, PendingIntent userActionIntent, int userId)1210     private Intent createErrorDialogIntent(int status, String installerPackageName,
1211             String appPackageName,
1212             long requiredStorageBytes, PendingIntent userActionIntent, int userId) {
1213         final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG);
1214         dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
1215         dialogIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
1216         if (requiredStorageBytes > 0) {
1217             dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes);
1218         }
1219         // Note that the userActionIntent is provided by the installer and is used only by the
1220         // system package installer as a follow-up action after the user confirms the dialog.
1221         if (userActionIntent != null) {
1222             dialogIntent.putExtra(Intent.EXTRA_INTENT, userActionIntent);
1223         }
1224         dialogIntent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
1225         // We fetch this label from the archive state because the installer might not be installed
1226         // anymore in an edge case.
1227         String installerTitle = getInstallerTitle(appPackageName, userId);
1228         if (installerTitle == null) {
1229             // Error already logged.
1230             return null;
1231         }
1232         dialogIntent.putExtra(EXTRA_INSTALLER_TITLE, installerTitle);
1233         return dialogIntent;
1234     }
1235 
getInstallerTitle(String appPackageName, int userId)1236     private String getInstallerTitle(String appPackageName, int userId) {
1237         PackageStateInternal packageState;
1238         try {
1239             packageState = getPackageState(appPackageName,
1240                     mPm.snapshotComputer(),
1241                     Process.SYSTEM_UID, userId);
1242         } catch (PackageManager.NameNotFoundException e) {
1243             Slog.e(TAG, TextUtils.formatSimple(
1244                     "notifyUnarchivalListener: Couldn't fetch package state for %s.",
1245                     appPackageName), e);
1246             return null;
1247         }
1248         ArchiveState archiveState = packageState.getUserStateOrDefault(userId).getArchiveState();
1249         if (archiveState == null) {
1250             Slog.e(TAG, TextUtils.formatSimple("notifyUnarchivalListener: App not archived %s.",
1251                     appPackageName));
1252             return null;
1253         }
1254         return archiveState.getInstallerTitle();
1255     }
1256 
1257     @NonNull
getPackageState(String packageName, Computer snapshot, int callingUid, int userId)1258     private static PackageStateInternal getPackageState(String packageName,
1259             Computer snapshot, int callingUid, int userId)
1260             throws PackageManager.NameNotFoundException {
1261         PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
1262                 userId);
1263         if (ps == null) {
1264             throw new PackageManager.NameNotFoundException(
1265                     TextUtils.formatSimple("Package %s not found.", packageName));
1266         }
1267         return ps;
1268     }
1269 
getLauncherApps()1270     private LauncherApps getLauncherApps() {
1271         if (mLauncherApps == null) {
1272             mLauncherApps = mContext.getSystemService(LauncherApps.class);
1273         }
1274         return mLauncherApps;
1275     }
1276 
getAppOpsManager()1277     private AppOpsManager getAppOpsManager() {
1278         if (mAppOpsManager == null) {
1279             mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
1280         }
1281         return mAppOpsManager;
1282     }
1283 
getUserManager()1284     private UserManager getUserManager() {
1285         if (mUserManager == null) {
1286             mUserManager = mContext.getSystemService(UserManager.class);
1287         }
1288         return mUserManager;
1289     }
1290 
storeArchiveState(String packageName, ArchiveState archiveState, int userId)1291     private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
1292             throws PackageManager.NameNotFoundException {
1293         synchronized (mPm.mLock) {
1294             PackageSetting packageSetting = getPackageSettingLocked(packageName, userId);
1295             packageSetting
1296                     .modifyUserState(userId)
1297                     .setArchiveState(archiveState);
1298         }
1299     }
1300 
1301     @NonNull
1302     @GuardedBy("mPm.mLock")
getPackageSettingLocked(String packageName, int userId)1303     private PackageSetting getPackageSettingLocked(String packageName, int userId)
1304             throws PackageManager.NameNotFoundException {
1305         PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
1306         // Shouldn't happen, we already verify presence of the package in getPackageState()
1307         if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
1308             throw new PackageManager.NameNotFoundException(
1309                     TextUtils.formatSimple("Package %s not found.", packageName));
1310         }
1311         return ps;
1312     }
1313 
sendFailureStatus(IntentSender statusReceiver, String packageName, String message)1314     private void sendFailureStatus(IntentSender statusReceiver, String packageName,
1315             String message) {
1316         Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName,
1317                 message));
1318         final Intent intent = new Intent();
1319         intent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
1320         intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
1321         intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
1322         sendIntent(statusReceiver, packageName, message, intent);
1323     }
1324 
sendIntent(IntentSender statusReceiver, String packageName, String message, Intent intent)1325     private void sendIntent(IntentSender statusReceiver, String packageName, String message,
1326             Intent intent) {
1327         try {
1328             final BroadcastOptions options = BroadcastOptions.makeBasic();
1329             options.setPendingIntentBackgroundActivityStartMode(
1330                     MODE_BACKGROUND_ACTIVITY_START_DENIED);
1331             statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null,
1332                     /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
1333         } catch (IntentSender.SendIntentException e) {
1334             Slog.e(
1335                     TAG,
1336                     TextUtils.formatSimple("Failed to send status for %s with message %s",
1337                             packageName, message),
1338                     e);
1339         }
1340     }
1341 
verifyCaller(int providedUid, int binderUid)1342     private static void verifyCaller(int providedUid, int binderUid) {
1343         if (providedUid != binderUid) {
1344             throw new SecurityException(
1345                     TextUtils.formatSimple(
1346                             "The UID %s of callerPackageName set by the caller doesn't match the "
1347                                     + "caller's actual UID %s.",
1348                             providedUid,
1349                             binderUid));
1350         }
1351     }
1352 
createIconsDir(String packageName, @UserIdInt int userId)1353     private static File createIconsDir(String packageName, @UserIdInt int userId)
1354             throws IOException {
1355         File iconsDir = getIconsDir(packageName, userId);
1356         if (!iconsDir.isDirectory()) {
1357             iconsDir.delete();
1358             iconsDir.mkdirs();
1359             if (!iconsDir.isDirectory()) {
1360                 throw new IOException("Unable to create directory " + iconsDir);
1361             }
1362             if (DEBUG) {
1363                 Slog.i(TAG, "Created icons directory at " + iconsDir.getAbsolutePath());
1364             }
1365         }
1366         SELinux.restorecon(iconsDir);
1367         return iconsDir;
1368     }
1369 
getIconsDir(String packageName, int userId)1370     private static File getIconsDir(String packageName, int userId) {
1371         return new File(
1372                 new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR),
1373                 packageName);
1374     }
1375 
bytesFromBitmapFile(Path path)1376     private static byte[] bytesFromBitmapFile(Path path) throws IOException {
1377         if (path == null) {
1378             return null;
1379         }
1380         // Technically we could just read the bytes, but we want to be sure we store the
1381         // right format.
1382         return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
1383     }
1384 
1385     @Nullable
getPackageNameFromIntent(@ullable Intent intent)1386     private static String getPackageNameFromIntent(@Nullable Intent intent) {
1387         if (intent == null) {
1388             return null;
1389         }
1390         if (intent.getPackage() != null) {
1391             return intent.getPackage();
1392         }
1393         if (intent.getComponent() != null) {
1394             return intent.getComponent().getPackageName();
1395         }
1396         return null;
1397     }
1398 
1399     /**
1400      * Creates serializable archived activities from existing ArchiveState.
1401      */
createArchivedActivities(ArchiveState archiveState)1402     static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState)
1403             throws IOException {
1404         var infos = archiveState.getActivityInfos();
1405         if (infos == null || infos.isEmpty()) {
1406             throw new IllegalArgumentException("No activities in archive state");
1407         }
1408 
1409         List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
1410         for (int i = 0, size = infos.size(); i < size; ++i) {
1411             var info = infos.get(i);
1412             if (info == null) {
1413                 continue;
1414             }
1415             var archivedActivity = new ArchivedActivityParcel();
1416             archivedActivity.title = info.getTitle();
1417             archivedActivity.originalComponentName = info.getOriginalComponentName();
1418             archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
1419             archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
1420                     info.getMonochromeIconBitmap());
1421             activities.add(archivedActivity);
1422         }
1423 
1424         if (activities.isEmpty()) {
1425             throw new IllegalArgumentException(
1426                     "Failed to extract title and icon of main activities");
1427         }
1428 
1429         return activities.toArray(new ArchivedActivityParcel[activities.size()]);
1430     }
1431 
1432     /**
1433      * Creates serializable archived activities from launcher activities.
1434      */
createArchivedActivities(List<LauncherActivityInfo> infos, int iconSize)1435     static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos,
1436             int iconSize) throws IOException {
1437         if (infos == null || infos.isEmpty()) {
1438             throw new IllegalArgumentException("No launcher activities");
1439         }
1440 
1441         List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
1442         for (int i = 0, size = infos.size(); i < size; ++i) {
1443             var info = infos.get(i);
1444             if (info == null) {
1445                 continue;
1446             }
1447             var archivedActivity = new ArchivedActivityParcel();
1448             archivedActivity.title = info.getLabel().toString();
1449             archivedActivity.originalComponentName = info.getComponentName();
1450             archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null :
1451                     bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize));
1452             // TODO(b/298452477) Handle monochrome icons.
1453             archivedActivity.monochromeIconBitmap = null;
1454             activities.add(archivedActivity);
1455         }
1456 
1457         if (activities.isEmpty()) {
1458             throw new IllegalArgumentException(
1459                     "Failed to extract title and icon of main activities");
1460         }
1461 
1462         return activities.toArray(new ArchivedActivityParcel[activities.size()]);
1463     }
1464 
1465     private class UnarchiveIntentSender extends IIntentSender.Stub {
1466         @Override
send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)1467         public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
1468                 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)
1469                 throws RemoteException {
1470             int status = intent.getExtras().getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS,
1471                     STATUS_PENDING_USER_ACTION);
1472             if (status == UNARCHIVAL_OK) {
1473                 return;
1474             }
1475             Intent extraIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
1476             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
1477             if (extraIntent != null && user != null
1478                     && mAppStateHelper.isAppTopVisible(
1479                     getCurrentLauncherPackageName(user.getIdentifier()))) {
1480                 extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1481                 mContext.startActivityAsUser(extraIntent, user);
1482             }
1483         }
1484     }
1485 }
1486