diff options
20 files changed, 764 insertions, 55 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 15411f41be30..c6b0034e84ae 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -13569,6 +13569,7 @@ package android.content.pm { field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES"; field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"; + field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff field public static final int SIGNATURE_MATCH = 0; // 0x0 field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0ea00f552c06..de6b3ae78a56 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1937,6 +1937,7 @@ package android.app.backup { method public android.os.IBinder getBinder(); method public long getCurrentRestoreSet(); method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor); + method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List<java.lang.String> getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List<java.lang.String>, int); method public int getRestoreData(android.os.ParcelFileDescriptor); method public int getTransportFlags(); method public int initializeDevice(); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 34a3ad19b270..a8412fa66609 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -358,7 +358,7 @@ interface IActivityManager { @UnsupportedAppUsage void resumeAppSwitches(); boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId, - int backupDestination); + int backupDestination, boolean useRestrictedMode); void backupAgentCreated(in String packageName, in IBinder agent, int userId); void unbindBackupAgent(in ApplicationInfo appInfo); int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index dcac59c19df4..5004c02194ea 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -16,6 +16,8 @@ package android.app.backup; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Intent; @@ -27,6 +29,7 @@ import android.os.RemoteException; import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.ITransportStatusCallback; import com.android.internal.infra.AndroidFuture; +import com.android.server.backup.Flags; import java.util.Arrays; import java.util.List; @@ -671,6 +674,22 @@ public class BackupTransport { } /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. + * + * @param operationType 0 for backup, 1 for restore. + * @return a subset of the {@code packageNames} passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + */ + @NonNull + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public List<String> getPackagesThatShouldNotUseRestrictedMode( + @NonNull List<String> packageNames, + @BackupAnnotations.OperationType int operationType) { + return List.of(); + } + + /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can * (if appropriate) decouple those framework-side changes from the BackupTransport @@ -977,5 +996,19 @@ public class BackupTransport { resultFuture.cancel(/* mayInterruptIfRunning */ true); } } + + @Override + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames, + int operationType, AndroidFuture<List<String>> resultFuture) { + try { + List<String> result = + BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames, + operationType); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } + } } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 13b13b9e4179..37295ac94823 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -193,6 +193,42 @@ public abstract class PackageManager { "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; /** + * <application> level {@link android.content.pm.PackageManager.Property} tag + * specifying whether the app should be put into the "restricted" backup mode when it's started + * for backup and restore operations. + * + * <p> See <a + * href="https://developer.android.com/identity/data/autobackup#ImplementingBackupAgent"> for + * information about restricted mode</a>. + * + * <p> Starting with Android 16 apps may not be started in restricted mode based on this + * property. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE" + * android:value="true|false"/> + * </application> + * </pre> + * + * <p>If this property is set, the operating system will respect it for now (see Note below). + * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps + * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the + * property defaults to {@code true}. For apps targeting SDK level + * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a + * decision dynamically. + * + * <p>Note: It's not recommended to set this property to {@code true} unless absolutely + * necessary. In a future Android version, this property may be deprecated in favor of removing + * restricted mode completely. + */ + @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = + "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; + + /** * Application level property that an app can specify to opt-out from having private data * directories both on the internal and external storages. * diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 21c7baab4e83..469ab48385da 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -410,4 +410,15 @@ oneway interface IBackupTransport { * however backups initiated by the framework will call this method to retrieve one. */ void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture); + + /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. The + * {@code resultFuture} should be completed with a subset of the packages passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + * + * @param operationType 0 for backup, 1 for restore. + */ + void getPackagesThatShouldNotUseRestrictedMode(in List<String> packageNames, int operationType, + in AndroidFuture<List<String>> resultFuture); } diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig index d53f949753d8..fcb7934f7ca0 100644 --- a/services/backup/flags.aconfig +++ b/services/backup/flags.aconfig @@ -60,3 +60,12 @@ flag { bug: "331749778" is_fixed_read_only: true } + +flag { + name: "enable_restricted_mode_changes" + namespace: "onboarding" + description: "Enables the new framework behavior of not putting apps in restricted mode for " + "B&R operations in certain cases." + bug: "376661510" + is_fixed_read_only: true +} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 466d477992b3..5de2fb30ac78 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -43,6 +43,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppGlobals; +import android.app.ApplicationThreadConstants; import android.app.IActivityManager; import android.app.IBackupAgent; import android.app.PendingIntent; @@ -59,6 +60,9 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -298,6 +302,15 @@ public class UserBackupManagerService { private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + /** + * Enables the OS making a decision on whether backup restricted mode should be used for apps + * that haven't explicitly opted in or out. See + * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) + public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510; + // Time delay for initialization operations that can be delayed so as not to consume too much // CPU on bring-up and increase time-to-UI. private static final long INITIALIZATION_DELAY_MILLIS = 3000; @@ -352,6 +365,9 @@ public class UserBackupManagerService { // Backups that we haven't started yet. Keys are package names. private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>(); + private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>(); + private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>(); + // locking around the pending-backup management private final Object mQueueLock = new Object(); @@ -523,7 +539,8 @@ public class UserBackupManagerService { @VisibleForTesting UserBackupManagerService(Context context, PackageManager packageManager, LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) { + BackupHandler backupHandler, BackupManagerConstants backupManagerConstants, + IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) { mContext = context; mUserId = 0; @@ -534,6 +551,8 @@ public class UserBackupManagerService { mFullBackupQueue = new ArrayList<>(); mBackupHandler = backupHandler; mConstants = backupManagerConstants; + mActivityManager = activityManager; + mActivityManagerInternal = activityManagerInternal; mBaseStateDir = null; mDataDir = null; @@ -543,13 +562,11 @@ public class UserBackupManagerService { mRunInitReceiver = null; mRunInitIntent = null; mAgentTimeoutParameters = null; - mActivityManagerInternal = null; mAlarmManager = null; mWakelock = null; mBackupPreferences = null; mBackupPasswordManager = null; mPackageManagerBinder = null; - mActivityManager = null; mBackupManagerBinder = null; mScheduledBackupEligibility = null; } @@ -1651,9 +1668,11 @@ public class UserBackupManagerService { synchronized (mAgentConnectLock) { mConnecting = true; mConnectedAgent = null; + boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode, + app.packageName); try { if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId, - backupDestination)) { + backupDestination, useRestrictedMode)) { Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app)); // success; wait for the agent to arrive @@ -3103,6 +3122,91 @@ public class UserBackupManagerService { } } + /** + * Marks the given set of packages as packages that should not be put into restricted mode if + * they are started for the given {@link BackupAnnotations.OperationType}. + */ + public void setNoRestrictedModePackages(Set<String> packageNames, + @BackupAnnotations.OperationType int opType) { + if (opType == BackupAnnotations.OperationType.BACKUP) { + mBackupNoRestrictedModePackages.clear(); + mBackupNoRestrictedModePackages.addAll(packageNames); + } else if (opType == BackupAnnotations.OperationType.RESTORE) { + mRestoreNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.addAll(packageNames); + } else { + throw new IllegalArgumentException("opType must be BACKUP or RESTORE"); + } + } + + /** + * Clears the list of packages that should not be put into restricted mode for either backup or + * restore. + */ + public void clearNoRestrictedModePackages() { + mBackupNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.clear(); + } + + /** + * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then + * its value is returned. If it hasn't and it targets an SDK below + * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then + * returns the decision made by the {@link android.app.backup.BackupTransport}. + * + * <p>When this method is called, we should have already asked the transport and cached its + * response in {@link #mBackupNoRestrictedModePackages} or + * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without + * any IPC to the transport. + */ + private boolean shouldUseRestrictedBackupModeForPackage( + @BackupAnnotations.OperationType int mode, String packageName) { + if (!Flags.enableRestrictedModeChanges()) { + return true; + } + + // Key/Value apps are never put in restricted mode. + if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL + || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) { + return false; + } + + try { + PackageManager.Property property = mPackageManager.getPropertyAsUser( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, + packageName, /* className= */ null, + mUserId); + if (property.isBoolean()) { + // If the package has explicitly specified, we won't ask the transport. + return property.getBoolean(); + } else { + Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + + "must be a boolean."); + } + } catch (NameNotFoundException e) { + // This is expected when the package has not defined the property in its manifest. + } + + // The package has not specified the property. The behavior depends on the package's + // targetSdk. + // <36 gets the old behavior of always using restricted mode. + if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName, + UserHandle.of(mUserId))) { + return true; + } + + // Apps targeting >=36 get the behavior decided by the transport. + // By this point, we should have asked the transport and cached its decision. + if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL + && mBackupNoRestrictedModePackages.contains(packageName)) + || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL + && mRestoreNoRestrictedModePackages.contains(packageName))) { + Slog.d(TAG, "Transport requested no restricted mode for: " + packageName); + return false; + } + return true; + } + private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index cca166b0939c..be9cdc8692cb 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -16,6 +16,8 @@ package com.android.server.backup.fullbackup; +import static android.app.backup.BackupAnnotations.OperationType.BACKUP; + import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; @@ -34,6 +36,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -388,6 +391,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } } + // We ask the transport which packages should not be put in restricted mode and cache + // the result in UBMS to be used later when the apps are started for backup. + setNoRestrictedModePackages(transport, mPackages); + // Set up to send data to the transport final int N = mPackages.size(); int chunkSizeInBytes = 8 * 1024; // 8KB @@ -694,6 +701,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba mUserBackupManagerService.scheduleNextFullBackupJob(backoff); } + // Clear this to avoid using the memory until reboot. + mUserBackupManagerService.clearNoRestrictedModePackages(); + Slog.i(TAG, "Full data backup pass finished."); mUserBackupManagerService.getWakelock().release(); } @@ -722,6 +732,21 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } } + private void setNoRestrictedModePackages(BackupTransportClient transport, + List<PackageInfo> packages) { + try { + Set<String> packageNames = new ArraySet<>(); + for (int i = 0; i < packages.size(); i++) { + packageNames.add(packages.get(i).packageName); + } + packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, + BACKUP); + mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP); + } catch (RemoteException e) { + Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport"); + } + } + // Run the backup and pipe it back to the given socket -- expects to run on // a standalone thread. The runner owns this half of the pipe, and closes // it to indicate EOD to the other end. diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index e536876f6cc3..5ee51a5aa189 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; @@ -482,6 +483,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { return; } + // We ask the transport which packages should not be put in restricted mode and cache + // the result in UBMS to be used later when the apps are started for restore. + setNoRestrictedModePackages(transport, packages); + RestoreDescription desc = transport.nextRestorePackage(); if (desc == null) { Slog.e(TAG, "No restore metadata available; halting"); @@ -1358,6 +1363,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Clear any ongoing session timeout. backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT); + // Clear this to avoid using the memory until reboot. + backupManagerService.clearNoRestrictedModePackages(); + // If we have a PM token, we must under all circumstances be sure to // handshake when we've finished. if (mPmToken > 0) { @@ -1819,4 +1827,20 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { return packageInfo; } + + @VisibleForTesting + void setNoRestrictedModePackages(BackupTransportClient transport, + PackageInfo[] packages) { + try { + Set<String> packageNames = new ArraySet<>(); + for (int i = 0; i < packages.length; i++) { + packageNames.add(packages[i].packageName); + } + packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, + RESTORE); + backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE); + } catch (RemoteException e) { + Slog.i(TAG, "Failed to retrieve restricted mode packages from transport"); + } + } } diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java index daf3415229ea..373811fef802 100644 --- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java @@ -17,6 +17,7 @@ package com.android.server.backup.transport; import android.annotation.Nullable; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.RestoreDescription; @@ -26,6 +27,7 @@ import android.content.pm.PackageInfo; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.backup.IBackupTransport; @@ -375,6 +377,26 @@ public class BackupTransportClient { } /** + * See + * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}. + */ + public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames, + @BackupAnnotations.OperationType + int operationType) throws RemoteException { + AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture(); + mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames), + operationType, + resultFuture); + List<String> resultList = getFutureResult(resultFuture); + Set<String> set = new ArraySet<>(); + if (resultList == null) { + return set; + } + set.addAll(resultList); + return set; + } + + /** * Allows the {@link TransportConnection} to notify this client * if the underlying transport has become unusable. If that happens * we want to cancel all active futures or callbacks. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index dfddc089e4a4..d3e5942834ee 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -478,7 +478,6 @@ import com.android.server.wm.WindowProcessController; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.VMRuntime; - import libcore.util.EmptyArray; import java.io.File; @@ -4493,16 +4492,11 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Unattached app died before backup, skipping"); final int userId = app.userId; final String packageName = app.info.packageName; - mHandler.post(new Runnable() { - @Override - public void run() { - try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnectedForUser(userId, packageName); - } catch (RemoteException e) { - // Can't happen; the backup manager is local - } + mHandler.post(() -> { + try { + getBackupManager().agentDisconnectedForUser(userId, packageName); + } catch (RemoteException e) { + // Can't happen; the backup manager is local } }); } @@ -4673,7 +4667,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) { isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID && ((backupTarget.backupMode == BackupRecord.RESTORE_FULL) - || (backupTarget.backupMode == BackupRecord.BACKUP_FULL)); + || (backupTarget.backupMode == BackupRecord.BACKUP_FULL)) + && backupTarget.useRestrictedMode; } final ActiveInstrumentation instr = app.getActiveInstrumentation(); @@ -13499,16 +13494,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (backupTarget != null && pid == backupTarget.app.getPid()) { if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App " + backupTarget.appInfo + " died during backup"); - mHandler.post(new Runnable() { - @Override - public void run() { - try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnectedForUser(app.userId, app.info.packageName); - } catch (RemoteException e) { - // can't happen; backup manager is local - } + mHandler.post(() -> { + try { + getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName); + } catch (RemoteException e) { + // can't happen; backup manager is local } }); } @@ -14011,7 +14001,7 @@ public class ActivityManagerService extends IActivityManager.Stub // instantiated. The backup agent will invoke backupAgentCreated() on the // activity manager to announce its creation. public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId, - @BackupDestination int backupDestination) { + @BackupDestination int backupDestination, boolean useRestrictedMode) { long startTimeNs = SystemClock.uptimeNanos(); if (DEBUG_BACKUP) { Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode @@ -14096,7 +14086,8 @@ public class ActivityManagerService extends IActivityManager.Stub + app.packageName + ": " + e); } - BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination); + BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination, + useRestrictedMode); ComponentName hostingName = (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) @@ -14122,8 +14113,9 @@ public class ActivityManagerService extends IActivityManager.Stub // process, etc, then mark it as being in full backup so that certain calls to the // process can be blocked. This is not reset to false anywhere because we kill the // process after the full backup is done and the ProcessRecord will vaporize anyway. - if (UserHandle.isApp(app.uid) && - backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) { + if (UserHandle.isApp(app.uid) + && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL + && r.useRestrictedMode) { proc.setInFullBackup(true); } r.app = proc; @@ -14221,9 +14213,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long oldIdent = Binder.clearCallingIdentity(); try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentConnectedForUser(userId, agentPackageName, agent); + getBackupManager().agentConnectedForUser(userId, agentPackageName, agent); } catch (RemoteException e) { // can't happen; the backup manager service is local } catch (Exception e) { @@ -19353,4 +19343,8 @@ public class ActivityManagerService extends IActivityManager.Stub } return token; } + + private IBackupManager getBackupManager() { + return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE)); + } } diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java index 0b056d7883bf..64cc6f0e66e3 100644 --- a/services/core/java/com/android/server/am/BackupRecord.java +++ b/services/core/java/com/android/server/am/BackupRecord.java @@ -32,15 +32,18 @@ final class BackupRecord { final int userId; // user for which backup is performed final int backupMode; // full backup / incremental / restore @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination + final boolean useRestrictedMode; // whether the app should be put into restricted backup mode ProcessRecord app; // where this agent is running or null // ----- Implementation ----- - BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) { + BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination, + boolean _useRestrictedMode) { appInfo = _appInfo; backupMode = _backupMode; userId = _userId; backupDestination = _backupDestination; + useRestrictedMode = _useRestrictedMode; } public String toString() { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index b51db137f293..98f738c38d63 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -351,7 +351,8 @@ class ProcessRecord implements WindowProcessListener { private String[] mIsolatedEntryPointArgs; /** - * Process is currently hosting a backup agent for backup or restore. + * Process is currently hosting a backup agent for backup or restore. Note that this is only set + * when the process is put into restricted backup mode. */ @GuardedBy("mService") private boolean mInFullBackup; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index dcbc23410fdb..5a872ea04bcc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -47,10 +47,8 @@ import static com.android.server.am.Flags.FLAG_AVOID_RESOLVING_TYPE; import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK; import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE; import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -80,6 +78,7 @@ import android.Manifest; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.ApplicationThreadConstants; import android.app.BackgroundStartPrivileges; import android.app.BroadcastOptions; import android.app.ForegroundServiceDelegationOptions; @@ -87,6 +86,7 @@ import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationChannel; import android.app.SyncNotedAppOp; +import android.app.backup.BackupAnnotations; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -111,6 +111,7 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -133,6 +134,7 @@ import com.android.server.am.ProcessList.IsolatedUidRange; import com.android.server.am.ProcessList.IsolatedUidRangeAllocator; import com.android.server.am.UidObserverController.ChangeRecord; import com.android.server.appop.AppOpsService; +import com.android.server.job.JobSchedulerInternal; import com.android.server.notification.NotificationManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerService; @@ -228,6 +230,7 @@ public class ActivityManagerServiceTest { @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal; @Mock private NotificationManagerInternal mNotificationManagerInternal; + @Mock private JobSchedulerInternal mJobSchedulerInternal; @Mock private ContentResolver mContentResolver; private TestInjector mInjector; @@ -249,6 +252,7 @@ public class ActivityManagerServiceTest { LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal); LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal); + LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal); doReturn(new ComponentName("", "")).when(mPackageManagerInternal) .getSystemUiServiceComponent(); @@ -308,6 +312,7 @@ public class ActivityManagerServiceTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); LocalServices.removeServiceForTest(NotificationManagerInternal.class); + LocalServices.removeServiceForTest(JobSchedulerInternal.class); if (mMockingSession != null) { mMockingSession.finishMocking(); @@ -1548,6 +1553,50 @@ public class ActivityManagerServiceTest { eq(notificationId), anyInt()); } + @SuppressWarnings("GuardedBy") + @Test + public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup() + throws Exception { + ActivityManagerService spyAms = spy(mAms); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = TEST_PACKAGE; + applicationInfo.processName = TEST_PACKAGE; + applicationInfo.uid = TEST_UID; + doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE), + anyLong(), anyInt()); + ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID); + doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID)); + + spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL, + UserHandle.USER_SYSTEM, + BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */ + true); + + assertThat(appRec.isInFullBackup()).isTrue(); + } + + @SuppressWarnings("GuardedBy") + @Test + public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup() + throws Exception { + ActivityManagerService spyAms = spy(mAms); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = TEST_PACKAGE; + applicationInfo.processName = TEST_PACKAGE; + applicationInfo.uid = TEST_UID; + doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE), + anyLong(), anyInt()); + ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID); + doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID)); + + spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL, + UserHandle.USER_SYSTEM, + BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */ + false); + + assertThat(appRec.isInFullBackup()).isFalse(); + } + private static class TestHandler extends Handler { private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index f82a86092064..94cf4cbc0f8c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -3306,7 +3306,7 @@ public class MockingOomAdjusterTests { if (Flags.pushGlobalStateToOomadjuster()) { mProcessStateController.setBackupTarget(app, app.userId); } else { - BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0); + BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true); backupTarget.app = app; doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index 65286d9aabc7..07f2188d30eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -18,9 +18,7 @@ package com.android.server.backup; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; - import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -32,20 +30,27 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; +import android.app.ApplicationThreadConstants; +import android.app.IActivityManager; import android.app.backup.BackupAgent; -import android.app.backup.BackupAnnotations; import android.app.backup.BackupAnnotations.BackupDestination; +import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.job.JobInfo; import android.app.job.JobScheduler; +import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.testing.TestableContext; import android.util.FeatureFlagUtils; @@ -68,7 +73,9 @@ import com.google.common.collect.ImmutableSet; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -77,8 +84,12 @@ import org.mockito.quality.Strictness; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.function.IntConsumer; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + @Presubmit @RunWith(AndroidJUnit4.class) public class UserBackupManagerServiceTest { @@ -88,6 +99,11 @@ public class UserBackupManagerServiceTest { private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100; @UserIdInt private static final int USER_ID = 0; + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock IBackupManagerMonitor mBackupManagerMonitor; @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @@ -99,10 +115,14 @@ public class UserBackupManagerServiceTest { @Mock JobScheduler mJobScheduler; @Mock BackupHandler mBackupHandler; @Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender; + @Mock IActivityManager mActivityManager; + @Mock + ActivityManagerInternal mActivityManagerInternal; private TestableContext mContext; private MockitoSession mSession; private TestBackupService mService; + private ApplicationInfo mTestPackageApplicationInfo; @Before public void setUp() throws Exception { @@ -120,12 +140,14 @@ public class UserBackupManagerServiceTest { mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP, PackageManager.PERMISSION_GRANTED); - mService = new TestBackupService(mContext, mPackageManager, mOperationStorage, - mTransportManager, mBackupHandler); + mService = new TestBackupService(); mService.setEnabled(true); mService.setSetupComplete(true); mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0); - } + + mTestPackageApplicationInfo = new ApplicationInfo(); + mTestPackageApplicationInfo.packageName = TEST_PACKAGE; + } @After public void tearDown() { @@ -298,9 +320,160 @@ public class UserBackupManagerServiceTest { new DataTypeResult(/* dataType */ "type_2")); mService.reportDelayedRestoreResult(TEST_PACKAGE, results); - verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults( - eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE)); + eq(packageInfo), eq(results), eq(OperationType.RESTORE)); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() + throws Exception { + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true, + TEST_PACKAGE, /* className= */ null)); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode() + throws Exception { + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false, + TEST_PACKAGE, /* className= */ null)); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.clearNoRestrictedModePackages(); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); } private static PackageInfo getPackageInfo(String packageName) { @@ -316,11 +489,9 @@ public class UserBackupManagerServiceTest { private volatile Thread mWorkerThread = null; - TestBackupService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler) { - super(context, packageManager, operationStorage, transportManager, backupHandler, - createConstants(context)); + TestBackupService() { + super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler, + createConstants(mContext), mActivityManager, mActivityManagerInternal); } private static BackupManagerConstants createConstants(Context context) { diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java index 94742537ed1a..331057398949 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java @@ -18,34 +18,95 @@ package com.android.server.backup.fullbackup; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.when; +import android.app.backup.BackupAnnotations; +import android.app.backup.BackupTransport; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; +import com.android.server.backup.BackupAgentTimeoutParameters; +import com.android.server.backup.OperationStorage; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; +import com.android.server.backup.transport.BackupTransportClient; +import com.android.server.backup.transport.TransportConnection; +import com.android.server.backup.utils.BackupEligibilityRules; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + @Presubmit @RunWith(AndroidJUnit4.class) public class PerformFullTransportBackupTaskTest { + private static final String TEST_PACKAGE_1 = "package1"; + private static final String TEST_PACKAGE_2 = "package2"; + + @Mock + BackupAgentTimeoutParameters mBackupAgentTimeoutParameters; + @Mock + BackupEligibilityRules mBackupEligibilityRules; @Mock UserBackupManagerService mBackupManagerService; @Mock + BackupTransportClient mBackupTransportClient; + @Mock + CountDownLatch mLatch; + @Mock + OperationStorage mOperationStorage; + @Mock + PackageManager mPackageManager; + @Mock + TransportConnection mTransportConnection; + @Mock TransportManager mTransportManager; + @Mock + UserBackupManagerService.BackupWakeLock mWakeLock; + + private final List<String> mEligiblePackages = new ArrayList<>(); + + private PerformFullTransportBackupTask mTask; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager); + when(mBackupManagerService.getQueueLock()).thenReturn("something!"); + when(mBackupManagerService.isEnabled()).thenReturn(true); + when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock); + when(mBackupManagerService.isSetupComplete()).thenReturn(true); + when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn( + mBackupAgentTimeoutParameters); when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager); + when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection); + when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient); + when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient); + when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn( + BackupTransport.TRANSPORT_ERROR); + when(mBackupEligibilityRules.appIsEligibleForBackup( + argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn( + true); + when(mBackupEligibilityRules.appGetsFullBackup( + argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn( + true); } @Test @@ -70,4 +131,49 @@ public class PerformFullTransportBackupTaskTest { /* backupEligibilityRules */ null); }); } + + @Test + public void run_setsAndClearsNoRestrictedModePackages() throws Exception { + mockPackageEligibleForFullBackup(TEST_PACKAGE_1); + mockPackageEligibleForFullBackup(TEST_PACKAGE_2); + createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2}); + when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(), + anyInt())).thenReturn(Set.of("package1")); + + mTask.run(); + + InOrder inOrder = inOrder(mBackupManagerService); + inOrder.verify(mBackupManagerService).setNoRestrictedModePackages( + eq(Set.of("package1")), + eq(BackupAnnotations.OperationType.BACKUP)); + inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages(); + } + + private void createTask(String[] packageNames) { + mTask = PerformFullTransportBackupTask + .newWithCurrentTransport( + mBackupManagerService, + mOperationStorage, + /* observer */ null, + /* whichPackages */ packageNames, + /* updateSchedule */ false, + /* runningJob */ null, + mLatch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + /* caller */ null, + mBackupEligibilityRules); + } + + private void mockPackageEligibleForFullBackup(String packageName) throws Exception { + mEligiblePackages.add(packageName); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + packageInfo.applicationInfo = appInfo; + when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn( + packageInfo); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 414532b88e22..055adf68ee0f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -23,8 +23,10 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupTransport; @@ -91,6 +93,8 @@ public class PerformUnifiedRestoreTaskTest { private UserBackupManagerService mBackupManagerService; @Mock private TransportConnection mTransportConnection; + @Mock + private BackupTransportClient mBackupTransportClient; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -151,6 +155,23 @@ public class PerformUnifiedRestoreTaskTest { } @Test + public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception { + PackageInfo packageInfo1 = new PackageInfo(); + packageInfo1.packageName = "package1"; + PackageInfo packageInfo2 = new PackageInfo(); + packageInfo2.packageName = "package2"; + when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(), + anyInt())).thenReturn(Set.of("package1")); + + mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient, + new PackageInfo[]{packageInfo1, packageInfo2}); + + verify(mBackupManagerService).setNoRestrictedModePackages( + eq(Set.of("package1")), + eq(BackupAnnotations.OperationType.RESTORE)); + } + + @Test public void testFilterExcludedKeys() throws Exception { when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))) .thenReturn(mExcludedkeys); diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java index 2d7d46f83c47..13e32078f609 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java @@ -19,7 +19,14 @@ package com.android.server.backup.transport; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; - +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.RestoreDescription; @@ -38,15 +45,31 @@ import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.ITransportStatusCallback; import com.android.internal.infra.AndroidFuture; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.List; +import java.util.Set; @Presubmit @RunWith(AndroidJUnit4.class) public class BackupTransportClientTest { + @Mock + IBackupTransport mMockBackupTransport; + + private BackupTransportClient mMockingTransportClient; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mMockingTransportClient = new BackupTransportClient( + mMockBackupTransport); + } + private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase { public final Object mLock = new Object(); @@ -128,6 +151,70 @@ public class BackupTransportClientTest { thread.join(); } + @Test + public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2")); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of("package1", "package2"), + OperationType.BACKUP); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode( + argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))), + eq(OperationType.BACKUP), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.RESTORE); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), + eq(OperationType.RESTORE), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.BACKUP); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), + eq(OperationType.BACKUP), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.BACKUP); + + assertThat(result).isEqualTo(Set.of()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2")); + + Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of("package1", "package2"), + OperationType.BACKUP); + + assertThat(result).isEqualTo(Set.of("package1", "package2")); + } + private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase { public final Object mLock = new Object(); @@ -158,7 +245,6 @@ public class BackupTransportClientTest { assertThat(status).isEqualTo(123); } - @Test public void testFinishBackup_completesLater_returnsStatus() throws Exception { TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder(); @@ -211,6 +297,14 @@ public class BackupTransportClientTest { thread.join(); } + private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList) + throws Exception { + doAnswer( + i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when( + mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(), + any()); + } + // Convenience layer so we only need to fake specific methods useful for each test case. private static class FakeTransportBinderBase implements IBackupTransport { @Override public void name(AndroidFuture<String> f) throws RemoteException {} @@ -258,6 +352,10 @@ public class BackupTransportClientTest { @Override public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture) throws RemoteException {} + @Override + public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames, + int operationType, AndroidFuture<List<String>> resultFuture) + throws RemoteException {} @Override public IBinder asBinder() { return null; } |