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