Merge "Remove usage of SK_MaxS32"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 31d2266..1a205d0 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -73,16 +73,12 @@
* Detach the notification supplied to
* {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
* The notification will remain shown even after JobScheduler stops the job.
- *
- * @hide
*/
public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
/**
* Cancel and remove the notification supplied to
* {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
* The notification will be removed from the notification shade.
- *
- * @hide
*/
public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index ed717c4..6998081 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -195,10 +195,35 @@
return;
}
+ if ("scheduling".equals(op)) {
+ setSchedulingEnabled(userId);
+ return;
+ }
+
System.err.println("Unknown command");
showUsage();
}
+ private void setSchedulingEnabled(int userId) {
+ String arg = nextArg();
+ if (arg == null) {
+ showUsage();
+ return;
+ }
+
+ try {
+ boolean enable = Boolean.parseBoolean(arg);
+ mBmgr.setFrameworkSchedulingEnabledForUser(userId, enable);
+ System.out.println(
+ "Backup scheduling is now "
+ + (enable ? "enabled" : "disabled")
+ + " for user "
+ + userId);
+ } catch (RemoteException e) {
+ handleRemoteException(e);
+ }
+ }
+
private void handleRemoteException(RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -944,6 +969,7 @@
System.err.println(" bmgr activate BOOL");
System.err.println(" bmgr activated");
System.err.println(" bmgr autorestore BOOL");
+ System.err.println(" bmgr scheduling BOOL");
System.err.println("");
System.err.println("The '--user' option specifies the user on which the operation is run.");
System.err.println("It must be the first argument before the operation.");
@@ -1021,6 +1047,9 @@
System.err.println("");
System.err.println("The 'autorestore' command enables or disables automatic restore when");
System.err.println("a new package is installed.");
+ System.err.println("");
+ System.err.println("The 'scheduling' command enables or disables backup scheduling in the");
+ System.err.println("framework.");
}
private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/core/api/current.txt b/core/api/current.txt
index a6446ff..4ec261c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8043,24 +8043,24 @@
field public static final int PACKAGE_POLICY_BLOCKLIST = 1; // 0x1
}
- public final class PolicyUpdateReason {
- ctor public PolicyUpdateReason(int);
- method public int getReasonCode();
- field public static final int REASON_CONFLICTING_ADMIN_POLICY = 0; // 0x0
- field public static final int REASON_UNKNOWN = -1; // 0xffffffff
+ public final class PolicyUpdateResult {
+ ctor public PolicyUpdateResult(int);
+ method public int getResultCode();
+ field public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1; // 0x1
+ field public static final int RESULT_FAILURE_UNKNOWN = -1; // 0xffffffff
+ field public static final int RESULT_SUCCESS = 0; // 0x0
}
public abstract class PolicyUpdatesReceiver extends android.content.BroadcastReceiver {
ctor public PolicyUpdatesReceiver();
- method public void onPolicyChanged(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, @NonNull android.app.admin.PolicyUpdateReason);
- method public void onPolicySetResult(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, int, @Nullable android.app.admin.PolicyUpdateReason);
+ method public void onPolicyChanged(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, @NonNull android.app.admin.PolicyUpdateResult);
+ method public void onPolicySetResult(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, @NonNull android.app.admin.PolicyUpdateResult);
method public final void onReceive(android.content.Context, android.content.Intent);
field public static final String ACTION_DEVICE_POLICY_CHANGED = "android.app.admin.action.DEVICE_POLICY_CHANGED";
field public static final String ACTION_DEVICE_POLICY_SET_RESULT = "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+ field public static final String EXTRA_INTENT_FILTER = "android.app.admin.extra.INTENT_FILTER";
field public static final String EXTRA_PACKAGE_NAME = "android.app.admin.extra.PACKAGE_NAME";
field public static final String EXTRA_PERMISSION_NAME = "android.app.admin.extra.PERMISSION_NAME";
- field public static final int POLICY_SET_RESULT_FAILURE = -1; // 0xffffffff
- field public static final int POLICY_SET_RESULT_SUCCESS = 0; // 0x0
}
public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
@@ -8650,6 +8650,8 @@
method public final void updateEstimatedNetworkBytes(@NonNull android.app.job.JobParameters, @NonNull android.app.job.JobWorkItem, long, long);
method public final void updateTransferredNetworkBytes(@NonNull android.app.job.JobParameters, long, long);
method public final void updateTransferredNetworkBytes(@NonNull android.app.job.JobParameters, @NonNull android.app.job.JobWorkItem, long, long);
+ field public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0; // 0x0
+ field public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1; // 0x1
field public static final String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE";
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ae032db..ce29937 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -311,6 +311,12 @@
@PermissionMethod
public abstract void enforceCallingPermission(@PermissionName String permission, String func);
+ /**
+ * Returns the current and target user ids as a {@link Pair}. Target user id will be
+ * {@link android.os.UserHandle#USER_NULL} if there is not an ongoing user switch.
+ */
+ public abstract Pair<Integer, Integer> getCurrentAndTargetUserIds();
+
/** Returns the current user id. */
public abstract int getCurrentUserId();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 33721a0..11584cc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3964,6 +3964,9 @@
* Called by a device owner, a profile owner of an organization-owned device or the system to
* get the Memory Tagging Extension (MTE) policy
*
+ * <a href="https://source.android.com/docs/security/test/memory-safety/arm-mte">
+ * Learn more about MTE</a>
+ *
* @throws SecurityException if caller is not device owner or profile owner of org-owned device
* or system uid, or if called on a parent instance
* @return the currently set MTE policy
@@ -3986,6 +3989,7 @@
*/
public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
+ // TODO: Expose this as SystemAPI once we add the query API
/**
* @hide
*/
@@ -4012,7 +4016,22 @@
/**
* @hide
*/
- public static final String USER_CONTROL_DISABLED_PACKAGES = "userControlDisabledPackages";
+ public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY =
+ "userControlDisabledPackages";
+
+
+ // TODO: Expose this as SystemAPI once we add the query API
+ /**
+ * @hide
+ */
+ public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY =
+ "persistentPreferredActivity";
+
+ // TODO: Expose this as SystemAPI once we add the query API
+ /**
+ * @hide
+ */
+ public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
/**
* This object is a single place to tack on invalidation and disable calls. All
diff --git a/core/java/android/app/admin/PolicyUpdateReason.java b/core/java/android/app/admin/PolicyUpdateReason.java
deleted file mode 100644
index 97d282d..0000000
--- a/core/java/android/app/admin/PolicyUpdateReason.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.admin;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Class containing the reason a policy (set from {@link DevicePolicyManager}) hasn't been enforced
- * (passed in to {@link PolicyUpdatesReceiver#onPolicySetResult}) or has changed (passed in to
- * {@link PolicyUpdatesReceiver#onPolicyChanged}).
- */
-public final class PolicyUpdateReason {
-
- /**
- * Reason code to indicate that the policy has not been enforced or has changed for an unknown
- * reason.
- */
- public static final int REASON_UNKNOWN = -1;
-
- /**
- * Reason code to indicate that the policy has not been enforced or has changed because another
- * admin has set a conflicting policy on the device.
- */
- public static final int REASON_CONFLICTING_ADMIN_POLICY = 0;
-
- /**
- * Reason codes for {@link #getReasonCode()}.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = { "REASON_" }, value = {
- REASON_UNKNOWN,
- REASON_CONFLICTING_ADMIN_POLICY,
- })
- public @interface ReasonCode {}
-
- private final int mReasonCode;
-
- /**
- * Constructor for {@code PolicyUpdateReason} that takes in a reason code describing why the
- * policy has changed.
- *
- * @param reasonCode Describes why the policy has changed.
- */
- public PolicyUpdateReason(@ReasonCode int reasonCode) {
- this.mReasonCode = reasonCode;
- }
-
- /**
- * Returns reason code for why a policy hasn't been applied or has changed.
- */
- @ReasonCode
- public int getReasonCode() {
- return mReasonCode;
- }
-}
diff --git a/core/java/android/app/admin/PolicyUpdateResult.java b/core/java/android/app/admin/PolicyUpdateResult.java
new file mode 100644
index 0000000..9e13e00
--- /dev/null
+++ b/core/java/android/app/admin/PolicyUpdateResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing the reason for the policy (set from {@link DevicePolicyManager}) update (e.g.
+ * success, failure reasons, etc.). This is passed in to
+ * {@link PolicyUpdatesReceiver#onPolicySetResult}) and
+ * {@link PolicyUpdatesReceiver#onPolicyChanged}).
+ */
+public final class PolicyUpdateResult {
+
+ /**
+ * Result code to indicate that the policy has not been enforced or has changed for an unknown
+ * reason.
+ */
+ public static final int RESULT_FAILURE_UNKNOWN = -1;
+
+ /**
+ * Result code to indicate that the policy has been changed to the desired value set by
+ * the admin.
+ */
+ public static final int RESULT_SUCCESS = 0;
+
+ /**
+ * Result code to indicate that the policy has not been enforced or has changed because another
+ * admin has set a conflicting policy on the device.
+ */
+ public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1;
+
+ /**
+ * Reason codes for {@link #getResultCode()}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "RESULT_" }, value = {
+ RESULT_FAILURE_UNKNOWN,
+ RESULT_SUCCESS,
+ RESULT_FAILURE_CONFLICTING_ADMIN_POLICY
+ })
+ public @interface ResultCode {}
+
+ private final int mResultCode;
+
+ /**
+ * Constructor for {@code PolicyUpdateReason} that takes in a result code describing why the
+ * policy has changed.
+ *
+ * @param resultCode Describes why the policy has changed.
+ */
+ public PolicyUpdateResult(@ResultCode int resultCode) {
+ this.mResultCode = resultCode;
+ }
+
+ /**
+ * Returns result code describing why the policy has changed.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+}
diff --git a/core/java/android/app/admin/PolicyUpdatesReceiver.java b/core/java/android/app/admin/PolicyUpdatesReceiver.java
index f7216e7..67de04c 100644
--- a/core/java/android/app/admin/PolicyUpdatesReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdatesReceiver.java
@@ -17,9 +17,7 @@
package android.app.admin;
import android.annotation.BroadcastBehavior;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -27,8 +25,6 @@
import android.os.Bundle;
import android.util.Log;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -50,31 +46,6 @@
private static String TAG = "PolicyUpdatesReceiver";
/**
- * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has been
- * set successfully.
- */
- public static final int POLICY_SET_RESULT_SUCCESS = 0;
-
- /**
- * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has NOT been
- * set, a {@link PolicyUpdateReason} will be passed in to {@link #onPolicySetResult} to indicate
- * the reason.
- */
- public static final int POLICY_SET_RESULT_FAILURE = -1;
-
- /**
- * Result codes passed in to {@link #onPolicySetResult}.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = { "POLICY_SET_RESULT_" }, value = {
- POLICY_SET_RESULT_SUCCESS,
- POLICY_SET_RESULT_FAILURE,
- })
- public @interface ResultCode {}
-
- /**
* Action for a broadcast sent to admins to communicate back the result of setting a policy in
* {@link DevicePolicyManager}.
*
@@ -125,6 +96,14 @@
"android.app.admin.extra.PERMISSION_NAME";
/**
+ * An {@link android.content.IntentFilter} extra holding the intent filter the policy relates
+ * to, (see {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_INTENT_FILTER =
+ "android.app.admin.extra.INTENT_FILTER";
+
+ /**
* @hide
*/
public static final String EXTRA_POLICY_CHANGED_KEY =
@@ -144,14 +123,8 @@
/**
* @hide
*/
- public static final String EXTRA_POLICY_SET_RESULT_KEY =
- "android.app.admin.extra.POLICY_SET_RESULT_KEY";
-
- /**
- * @hide
- */
- public static final String EXTRA_POLICY_UPDATE_REASON_KEY =
- "android.app.admin.extra.POLICY_UPDATE_REASON_KEY";
+ public static final String EXTRA_POLICY_UPDATE_RESULT_KEY =
+ "android.app.admin.extra.POLICY_UPDATE_RESULT_KEY";
/**
* @hide
@@ -172,7 +145,7 @@
case ACTION_DEVICE_POLICY_SET_RESULT:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
- getTargetUser(intent), getPolicyResult(intent), getFailureReason(intent));
+ getTargetUser(intent), getPolicyChangedReason(intent));
break;
case ACTION_DEVICE_POLICY_CHANGED:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
@@ -197,17 +170,6 @@
/**
* @hide
*/
- @ResultCode
- static int getPolicyResult(Intent intent) {
- if (!intent.hasExtra(EXTRA_POLICY_SET_RESULT_KEY)) {
- throw new IllegalArgumentException("Result has to be provided.");
- }
- return intent.getIntExtra(EXTRA_POLICY_SET_RESULT_KEY, POLICY_SET_RESULT_FAILURE);
- }
-
- /**
- * @hide
- */
@NonNull
static Bundle getPolicyExtraBundle(Intent intent) {
Bundle bundle = intent.getBundleExtra(EXTRA_POLICY_BUNDLE_KEY);
@@ -217,22 +179,14 @@
/**
* @hide
*/
- @Nullable
- static PolicyUpdateReason getFailureReason(Intent intent) {
- if (getPolicyResult(intent) != POLICY_SET_RESULT_FAILURE) {
- return null;
- }
- return getPolicyChangedReason(intent);
- }
-
- /**
- * @hide
- */
@NonNull
- static PolicyUpdateReason getPolicyChangedReason(Intent intent) {
+ static PolicyUpdateResult getPolicyChangedReason(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_UPDATE_RESULT_KEY)) {
+ throw new IllegalArgumentException("PolicyUpdateResult has to be provided.");
+ }
int reasonCode = intent.getIntExtra(
- EXTRA_POLICY_UPDATE_REASON_KEY, PolicyUpdateReason.REASON_UNKNOWN);
- return new PolicyUpdateReason(reasonCode);
+ EXTRA_POLICY_UPDATE_RESULT_KEY, PolicyUpdateResult.RESULT_FAILURE_UNKNOWN);
+ return new PolicyUpdateResult(reasonCode);
}
/**
@@ -268,19 +222,18 @@
* Each policy will document the required additional params if
* needed.
* @param targetUser The {@link TargetUser} which this policy relates to.
- * @param result Indicates whether the policy has been set successfully,
- * (see {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_SUCCESS} and
- * {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_FAILURE}).
- * @param reason Indicates the reason the policy failed to apply, {@code null} if the policy was
- * applied successfully.
+ * @param policyUpdateResult Indicates whether the policy has been set successfully
+ * ({@link PolicyUpdateResult#RESULT_SUCCESS}) or the reason it
+ * failed to apply (e.g.
+ * {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY},
+ * etc).
*/
public void onPolicySetResult(
@NonNull Context context,
@NonNull String policyKey,
@NonNull Bundle additionalPolicyParams,
@NonNull TargetUser targetUser,
- @ResultCode int result,
- @Nullable PolicyUpdateReason reason) {}
+ @NonNull PolicyUpdateResult policyUpdateResult) {}
// TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
// TODO(b/261430877): Add javadocs to explain when will this get triggered
@@ -302,12 +255,17 @@
* Each policy will document the required additional params if
* needed.
* @param targetUser The {@link TargetUser} which this policy relates to.
- * @param reason Indicates the reason the policy value has changed.
+ * @param policyUpdateResult Indicates the reason the policy value has changed
+ * (e.g. {@link PolicyUpdateResult#RESULT_SUCCESS} if the policy has
+ * changed to the value set by the admin,
+ * {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY}
+ * if the policy has changed because another admin has set a
+ * conflicting policy, etc)
*/
public void onPolicyChanged(
@NonNull Context context,
@NonNull String policyKey,
@NonNull Bundle additionalPolicyParams,
@NonNull TargetUser targetUser,
- @NonNull PolicyUpdateReason reason) {}
+ @NonNull PolicyUpdateResult policyUpdateResult) {}
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index bad282e..ddeb2f9 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,7 +16,6 @@
package android.app.backup;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -38,8 +37,6 @@
import android.util.Log;
import android.util.Pair;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -410,6 +407,33 @@
}
/**
+ * Enable/disable the framework backup scheduling entirely for the current user. When disabled,
+ * no Key/Value or Full backup jobs will be scheduled by the Android framework.
+ *
+ * <p>Note: This does not disable backups: only their scheduling is affected and backups can
+ * still be triggered manually.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.BACKUP,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+ public void setFrameworkSchedulingEnabled(boolean isEnabled) {
+ checkServiceBinder();
+ if (sService == null) {
+ Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
+ return;
+ }
+
+ try {
+ sService.setFrameworkSchedulingEnabledForUser(mContext.getUserId(), isEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
+ }
+ }
+
+ /**
* Report whether the backup mechanism is currently enabled.
*
* @hide
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index aeb4987..041c2a7 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -155,6 +155,22 @@
*/
void setBackupEnabledForUser(int userId, boolean isEnabled);
+
+ /**
+ * Enable/disable the framework backup scheduling entirely. When disabled, no Key/Value or Full
+ * backup jobs will be scheduled by the Android framework.
+ *
+ * <p>Note: This does not disable backups: only their scheduling is affected and backups can
+ * still be triggered manually.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method. If
+ * {@code userId} is different from the calling user id, then the caller must additionally hold
+ * the android.permission.INTERACT_ACROSS_USERS_FULL permission.
+ *
+ * @param userId The user for which backup scheduling should be enabled/disabled.
+ */
+ void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled);
+
/**
* {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id.
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 0e37c87..047f8c1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -276,6 +276,8 @@
void clearPackagePersistentPreferredActivities(String packageName, int userId);
+ void clearPersistentPreferredActivity(in IntentFilter filter, int userId);
+
void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage,
int sourceUserId, int targetUserId, int flags);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9a848af..b510fad 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8660,6 +8660,12 @@
public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
/**
+ * Controls whether framework backup scheduling is enabled.
+ * @hide
+ */
+ public static final String BACKUP_SCHEDULING_ENABLED = "backup_scheduling_enabled";
+
+ /**
* Indicates whether settings backup has been fully provisioned.
* Type: int ( 0 = unprovisioned, 1 = fully provisioned )
* @hide
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 970728f..56da8e0 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -3,6 +3,7 @@
per-file *Chooser* = file:/packages/SystemUI/OWNERS
per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS
per-file AbstractMultiProfilePagerAdapter.java = file:/packages/SystemUI/OWNERS
+per-file *EmptyStateProvider.java = file:/packages/SystemUI/OWNERS
per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 8fb345b..a704eb3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -337,4 +337,11 @@
* @param leftOrTop indicates where the stage split is.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop);
+
+ /**
+ * Shows the media output switcher dialog.
+ *
+ * @param packageName of the session for which the output switcher is shown.
+ */
+ void showMediaOutputSwitcher(String packageName);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index 44a37f4..d4d2b48 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -32,6 +33,9 @@
import com.android.settingslib.R;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
/**
* Utility methods for working with display density.
@@ -70,120 +74,169 @@
*/
private static final int MIN_DIMENSION_DP = 320;
- private final String[] mEntries;
- private final int[] mValues;
+ private static final Predicate<DisplayInfo> INTERNAL_ONLY =
+ (info) -> info.type == Display.TYPE_INTERNAL;
- private final int mDefaultDensity;
- private final int mCurrentIndex;
+ private final Predicate<DisplayInfo> mPredicate;
+
+ private final DisplayManager mDisplayManager;
+
+ /**
+ * The text description of the density values of the default display.
+ */
+ private String[] mDefaultDisplayDensityEntries;
+
+ /**
+ * The density values of the default display.
+ */
+ private int[] mDefaultDisplayDensityValues;
+
+ /**
+ * The density values, indexed by display unique ID.
+ */
+ private final Map<String, int[]> mValuesPerDisplay = new HashMap();
+
+ private int mDefaultDensityForDefaultDisplay;
+ private int mCurrentIndex = -1;
public DisplayDensityUtils(Context context) {
- final int defaultDensity = DisplayDensityUtils.getDefaultDisplayDensity(
- Display.DEFAULT_DISPLAY);
- if (defaultDensity <= 0) {
- mEntries = null;
- mValues = null;
- mDefaultDensity = 0;
- mCurrentIndex = -1;
- return;
- }
+ this(context, INTERNAL_ONLY);
+ }
- final Resources res = context.getResources();
- DisplayInfo info = new DisplayInfo();
- context.getDisplayNoVerify().getDisplayInfo(info);
+ /**
+ * Creates an instance that stores the density values for the displays that satisfy
+ * the predicate.
+ * @param context The context
+ * @param predicate Determines what displays the density should be set for. The default display
+ * must satisfy this predicate.
+ */
+ public DisplayDensityUtils(Context context, Predicate predicate) {
+ mPredicate = predicate;
+ mDisplayManager = context.getSystemService(DisplayManager.class);
- final int currentDensity = info.logicalDensityDpi;
- int currentDensityIndex = -1;
-
- // Compute number of "larger" and "smaller" scales for this display.
- final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
- final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
- final float maxScaleDimen = context.getResources().getFraction(
- R.fraction.display_density_max_scale, 1, 1);
- final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
- final float minScale = context.getResources().getFraction(
- R.fraction.display_density_min_scale, 1, 1);
- final float minScaleInterval = context.getResources().getFraction(
- R.fraction.display_density_min_scale_interval, 1, 1);
- final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
- 0, SUMMARIES_LARGER.length);
- final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
- 0, SUMMARIES_SMALLER.length);
-
- String[] entries = new String[1 + numSmaller + numLarger];
- int[] values = new int[entries.length];
- int curIndex = 0;
-
- if (numSmaller > 0) {
- final float interval = (1 - minScale) / numSmaller;
- for (int i = numSmaller - 1; i >= 0; i--) {
- // Round down to a multiple of 2 by truncating the low bit.
- final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
- if (currentDensity == density) {
- currentDensityIndex = curIndex;
- }
- entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
- values[curIndex] = density;
- curIndex++;
+ for (Display display : mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+ DisplayInfo info = new DisplayInfo();
+ if (!display.getDisplayInfo(info)) {
+ Log.w(LOG_TAG, "Cannot fetch display info for display " + display.getDisplayId());
+ continue;
}
- }
-
- if (currentDensity == defaultDensity) {
- currentDensityIndex = curIndex;
- }
- values[curIndex] = defaultDensity;
- entries[curIndex] = res.getString(SUMMARY_DEFAULT);
- curIndex++;
-
- if (numLarger > 0) {
- final float interval = (maxScale - 1) / numLarger;
- for (int i = 0; i < numLarger; i++) {
- // Round down to a multiple of 2 by truncating the low bit.
- final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
- if (currentDensity == density) {
- currentDensityIndex = curIndex;
+ if (!mPredicate.test(info)) {
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Predicate must not filter out the default "
+ + "display.");
}
- values[curIndex] = density;
- entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
- curIndex++;
+ continue;
}
+
+ final int defaultDensity = DisplayDensityUtils.getDefaultDensityForDisplay(
+ display.getDisplayId());
+ if (defaultDensity <= 0) {
+ Log.w(LOG_TAG, "Cannot fetch default density for display "
+ + display.getDisplayId());
+ continue;
+ }
+
+ final Resources res = context.getResources();
+
+ final int currentDensity = info.logicalDensityDpi;
+ int currentDensityIndex = -1;
+
+ // Compute number of "larger" and "smaller" scales for this display.
+ final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
+ final int maxDensity =
+ DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
+ final float maxScaleDimen = context.getResources().getFraction(
+ R.fraction.display_density_max_scale, 1, 1);
+ final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
+ final float minScale = context.getResources().getFraction(
+ R.fraction.display_density_min_scale, 1, 1);
+ final float minScaleInterval = context.getResources().getFraction(
+ R.fraction.display_density_min_scale_interval, 1, 1);
+ final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
+ 0, SUMMARIES_LARGER.length);
+ final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
+ 0, SUMMARIES_SMALLER.length);
+
+ String[] entries = new String[1 + numSmaller + numLarger];
+ int[] values = new int[entries.length];
+ int curIndex = 0;
+
+ if (numSmaller > 0) {
+ final float interval = (1 - minScale) / numSmaller;
+ for (int i = numSmaller - 1; i >= 0; i--) {
+ // Round down to a multiple of 2 by truncating the low bit.
+ final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
+ if (currentDensity == density) {
+ currentDensityIndex = curIndex;
+ }
+ entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
+ values[curIndex] = density;
+ curIndex++;
+ }
+ }
+
+ if (currentDensity == defaultDensity) {
+ currentDensityIndex = curIndex;
+ }
+ values[curIndex] = defaultDensity;
+ entries[curIndex] = res.getString(SUMMARY_DEFAULT);
+ curIndex++;
+
+ if (numLarger > 0) {
+ final float interval = (maxScale - 1) / numLarger;
+ for (int i = 0; i < numLarger; i++) {
+ // Round down to a multiple of 2 by truncating the low bit.
+ final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
+ if (currentDensity == density) {
+ currentDensityIndex = curIndex;
+ }
+ values[curIndex] = density;
+ entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
+ curIndex++;
+ }
+ }
+
+ final int displayIndex;
+ if (currentDensityIndex >= 0) {
+ displayIndex = currentDensityIndex;
+ } else {
+ // We don't understand the current density. Must have been set by
+ // someone else. Make room for another entry...
+ int newLength = values.length + 1;
+ values = Arrays.copyOf(values, newLength);
+ values[curIndex] = currentDensity;
+
+ entries = Arrays.copyOf(entries, newLength);
+ entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
+
+ displayIndex = curIndex;
+ }
+
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDefaultDensityForDefaultDisplay = defaultDensity;
+ mCurrentIndex = displayIndex;
+ mDefaultDisplayDensityEntries = entries;
+ mDefaultDisplayDensityValues = values;
+ }
+ mValuesPerDisplay.put(info.uniqueId, values);
}
-
- final int displayIndex;
- if (currentDensityIndex >= 0) {
- displayIndex = currentDensityIndex;
- } else {
- // We don't understand the current density. Must have been set by
- // someone else. Make room for another entry...
- int newLength = values.length + 1;
- values = Arrays.copyOf(values, newLength);
- values[curIndex] = currentDensity;
-
- entries = Arrays.copyOf(entries, newLength);
- entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
-
- displayIndex = curIndex;
- }
-
- mDefaultDensity = defaultDensity;
- mCurrentIndex = displayIndex;
- mEntries = entries;
- mValues = values;
}
- public String[] getEntries() {
- return mEntries;
+ public String[] getDefaultDisplayDensityEntries() {
+ return mDefaultDisplayDensityEntries;
}
- public int[] getValues() {
- return mValues;
+ public int[] getDefaultDisplayDensityValues() {
+ return mDefaultDisplayDensityValues;
}
- public int getCurrentIndex() {
+ public int getCurrentIndexForDefaultDisplay() {
return mCurrentIndex;
}
- public int getDefaultDensity() {
- return mDefaultDensity;
+ public int getDefaultDensityForDefaultDisplay() {
+ return mDefaultDensityForDefaultDisplay;
}
/**
@@ -193,7 +246,7 @@
* @return the default density of the specified display, or {@code -1} if
* the display does not exist or the density could not be obtained
*/
- private static int getDefaultDisplayDensity(int displayId) {
+ private static int getDefaultDensityForDisplay(int displayId) {
try {
final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
return wm.getInitialDisplayDensity(displayId);
@@ -203,19 +256,31 @@
}
/**
- * Asynchronously applies display density changes to the specified display.
+ * Asynchronously applies display density changes to the displays that satisfy the predicate.
* <p>
* The change will be applied to the user specified by the value of
* {@link UserHandle#myUserId()} at the time the method is called.
- *
- * @param displayId the identifier of the display to modify
*/
- public static void clearForcedDisplayDensity(final int displayId) {
+ public void clearForcedDisplayDensity() {
final int userId = UserHandle.myUserId();
AsyncTask.execute(() -> {
try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.clearForcedDisplayDensityForUser(displayId, userId);
+ for (Display display : mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+ int displayId = display.getDisplayId();
+ DisplayInfo info = new DisplayInfo();
+ if (!display.getDisplayInfo(info)) {
+ Log.w(LOG_TAG, "Unable to clear forced display density setting "
+ + "for display " + displayId);
+ continue;
+ }
+ if (!mPredicate.test(info)) {
+ continue;
+ }
+
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.clearForcedDisplayDensityForUser(displayId, userId);
+ }
} catch (RemoteException exc) {
Log.w(LOG_TAG, "Unable to clear forced display density setting");
}
@@ -223,20 +288,39 @@
}
/**
- * Asynchronously applies display density changes to the specified display.
+ * Asynchronously applies display density changes to the displays that satisfy the predicate.
* <p>
* The change will be applied to the user specified by the value of
* {@link UserHandle#myUserId()} at the time the method is called.
*
- * @param displayId the identifier of the display to modify
- * @param density the density to force for the specified display
+ * @param index The index of the density value
*/
- public static void setForcedDisplayDensity(final int displayId, final int density) {
+ public void setForcedDisplayDensity(final int index) {
final int userId = UserHandle.myUserId();
AsyncTask.execute(() -> {
try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.setForcedDisplayDensityForUser(displayId, density, userId);
+ for (Display display : mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+ int displayId = display.getDisplayId();
+ DisplayInfo info = new DisplayInfo();
+ if (!display.getDisplayInfo(info)) {
+ Log.w(LOG_TAG, "Unable to save forced display density setting "
+ + "for display " + displayId);
+ continue;
+ }
+ if (!mPredicate.test(info)) {
+ continue;
+ }
+ if (!mValuesPerDisplay.containsKey(info.uniqueId)) {
+ Log.w(LOG_TAG, "Unable to save forced display density setting "
+ + "for display " + info.uniqueId);
+ continue;
+ }
+
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.setForcedDisplayDensityForUser(displayId,
+ mValuesPerDisplay.get(info.uniqueId)[index], userId);
+ }
} catch (RemoteException exc) {
Log.w(LOG_TAG, "Unable to save forced display density setting");
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4365a9b..5ee36f3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -695,6 +695,7 @@
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
Settings.Secure.BACKUP_PROVISIONED,
+ Settings.Secure.BACKUP_SCHEDULING_ENABLED,
Settings.Secure.BACKUP_TRANSPORT,
Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 59f68f7..9cbc64e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.log.SessionTracker
+import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
import com.android.systemui.media.RingtonePlayer
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
@@ -218,6 +219,12 @@
@ClassKey(ToastUI::class)
abstract fun bindToastUI(service: ToastUI): CoreStartable
+ /** Inject into MediaOutputSwitcherDialogUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaOutputSwitcherDialogUI::class)
+ abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
+
/** Inject into VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index c73387b..72c7cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -119,6 +119,7 @@
private boolean mListening;
private boolean mListeningTouchScreenSensors;
private boolean mListeningProxSensors;
+ private boolean mListeningAodOnlySensors;
private boolean mUdfpsEnrolled;
@DevicePostureController.DevicePostureInt
@@ -187,7 +188,8 @@
dozeParameters.getPulseOnSigMotion(),
DozeLog.PULSE_REASON_SENSOR_SIGMOTION,
false /* touchCoords */,
- false /* touchscreen */),
+ false /* touchscreen */
+ ),
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PICK_UP_GESTURE,
@@ -198,14 +200,17 @@
false /* touchscreen */,
false /* ignoresSetting */,
false /* requires prox */,
- true /* immediatelyReRegister */),
+ true /* immediatelyReRegister */,
+ false /* requiresAod */
+ ),
new TriggerSensor(
findSensor(config.doubleTapSensorType()),
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
true /* configured */,
DozeLog.REASON_SENSOR_DOUBLE_TAP,
dozeParameters.doubleTapReportsTouchCoordinates(),
- true /* touchscreen */),
+ true /* touchscreen */
+ ),
new TriggerSensor(
findSensors(config.tapSensorTypeMapping()),
Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
@@ -217,7 +222,9 @@
false /* ignoresSetting */,
dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
true /* immediatelyReRegister */,
- mDevicePosture),
+ mDevicePosture,
+ false
+ ),
new TriggerSensor(
findSensor(config.longPressSensorType()),
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
@@ -228,7 +235,9 @@
true /* touchscreen */,
false /* ignoresSetting */,
dozeParameters.longPressUsesProx() /* requiresProx */,
- true /* immediatelyReRegister */),
+ true /* immediatelyReRegister */,
+ false /* requiresAod */
+ ),
new TriggerSensor(
findSensor(config.udfpsLongPressSensorType()),
"doze_pulse_on_auth",
@@ -239,7 +248,9 @@
true /* touchscreen */,
false /* ignoresSetting */,
dozeParameters.longPressUsesProx(),
- false /* immediatelyReRegister */),
+ false /* immediatelyReRegister */,
+ true /* requiresAod */
+ ),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
@@ -247,7 +258,8 @@
&& mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT),
DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
- false /* touchscreen */),
+ false /* touchscreen */
+ ),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
@@ -255,7 +267,8 @@
DozeLog.PULSE_REASON_SENSOR_WAKE_REACH,
false /* reports touch coordinates */,
false /* touchscreen */,
- mConfig.getWakeLockScreenDebounce()),
+ mConfig.getWakeLockScreenDebounce()
+ ),
new TriggerSensor(
findSensor(config.quickPickupSensorType()),
Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
@@ -266,7 +279,9 @@
false /* requiresTouchscreen */,
false /* ignoresSetting */,
false /* requiresProx */,
- true /* immediatelyReRegister */),
+ true /* immediatelyReRegister */,
+ false /* requiresAod */
+ ),
};
setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
@@ -360,29 +375,36 @@
/**
* If sensors should be registered and sending signals.
*/
- public void setListening(boolean listen, boolean includeTouchScreenSensors) {
- if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors) {
+ public void setListening(boolean listen, boolean includeTouchScreenSensors,
+ boolean includeAodOnlySensors) {
+ if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors
+ && mListeningAodOnlySensors == includeAodOnlySensors) {
return;
}
mListening = listen;
mListeningTouchScreenSensors = includeTouchScreenSensors;
+ mListeningAodOnlySensors = includeAodOnlySensors;
updateListening();
}
/**
* If sensors should be registered and sending signals.
*/
- public void setListening(boolean listen, boolean includeTouchScreenSensors,
- boolean lowPowerStateOrOff) {
+ public void setListeningWithPowerState(boolean listen, boolean includeTouchScreenSensors,
+ boolean includeAodRequiringSensors, boolean lowPowerStateOrOff) {
final boolean shouldRegisterProxSensors =
!mSelectivelyRegisterProxSensors || lowPowerStateOrOff;
- if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors
- && mListeningProxSensors == shouldRegisterProxSensors) {
+ if (mListening == listen
+ && mListeningTouchScreenSensors == includeTouchScreenSensors
+ && mListeningProxSensors == shouldRegisterProxSensors
+ && mListeningAodOnlySensors == includeAodRequiringSensors
+ ) {
return;
}
mListening = listen;
mListeningTouchScreenSensors = includeTouchScreenSensors;
mListeningProxSensors = shouldRegisterProxSensors;
+ mListeningAodOnlySensors = includeAodRequiringSensors;
updateListening();
}
@@ -394,7 +416,8 @@
for (TriggerSensor s : mTriggerSensors) {
boolean listen = mListening
&& (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
- && (!s.mRequiresProx || mListeningProxSensors);
+ && (!s.mRequiresProx || mListeningProxSensors)
+ && (!s.mRequiresAod || mListeningAodOnlySensors);
s.setListening(listen);
if (listen) {
anyListening = true;
@@ -502,6 +525,9 @@
private final boolean mRequiresTouchscreen;
private final boolean mRequiresProx;
+ // Whether the sensor should only register if the device is in AOD
+ private final boolean mRequiresAod;
+
// Whether to immediately re-register this sensor after the sensor is triggered.
// If false, the sensor registration will be updated on the next AOD state transition.
private final boolean mImmediatelyReRegister;
@@ -530,7 +556,8 @@
requiresTouchscreen,
false /* ignoresSetting */,
false /* requiresProx */,
- true /* immediatelyReRegister */
+ true /* immediatelyReRegister */,
+ false
);
}
@@ -544,7 +571,8 @@
boolean requiresTouchscreen,
boolean ignoresSetting,
boolean requiresProx,
- boolean immediatelyReRegister
+ boolean immediatelyReRegister,
+ boolean requiresAod
) {
this(
new Sensor[]{ sensor },
@@ -557,7 +585,8 @@
ignoresSetting,
requiresProx,
immediatelyReRegister,
- DevicePostureController.DEVICE_POSTURE_UNKNOWN
+ DevicePostureController.DEVICE_POSTURE_UNKNOWN,
+ requiresAod
);
}
@@ -572,7 +601,8 @@
boolean ignoresSetting,
boolean requiresProx,
boolean immediatelyReRegister,
- @DevicePostureController.DevicePostureInt int posture
+ @DevicePostureController.DevicePostureInt int posture,
+ boolean requiresAod
) {
mSensors = sensors;
mSetting = setting;
@@ -583,6 +613,7 @@
mRequiresTouchscreen = requiresTouchscreen;
mIgnoresSetting = ignoresSetting;
mRequiresProx = requiresProx;
+ mRequiresAod = requiresAod;
mPosture = posture;
mImmediatelyReRegister = immediatelyReRegister;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b95c3f3..b709608 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -111,6 +111,7 @@
private boolean mWantProxSensor;
private boolean mWantTouchScreenSensors;
private boolean mWantSensors;
+ private boolean mInAod;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -460,12 +461,19 @@
mDozeSensors.requestTemporaryDisable();
break;
case DOZE:
- case DOZE_AOD:
mAodInterruptRunnable = null;
- mWantProxSensor = newState != DozeMachine.State.DOZE;
+ mWantProxSensor = false;
mWantSensors = true;
mWantTouchScreenSensors = true;
- if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
+ mInAod = false;
+ break;
+ case DOZE_AOD:
+ mAodInterruptRunnable = null;
+ mWantProxSensor = true;
+ mWantSensors = true;
+ mWantTouchScreenSensors = true;
+ mInAod = true;
+ if (!sWakeDisplaySensorState) {
onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE);
}
break;
@@ -491,7 +499,7 @@
break;
default:
}
- mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
+ mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, mInAod);
}
private void registerCallbacks() {
@@ -510,11 +518,12 @@
private void stopListeningToAllTriggers() {
unregisterCallbacks();
- mDozeSensors.setListening(false, false);
+ mDozeSensors.setListening(false, false, false);
mDozeSensors.setProxListening(false);
mWantSensors = false;
mWantProxSensor = false;
mWantTouchScreenSensors = false;
+ mInAod = false;
}
@Override
@@ -523,7 +532,8 @@
final boolean lowPowerStateOrOff = state == Display.STATE_DOZE
|| state == Display.STATE_DOZE_SUSPEND || state == Display.STATE_OFF;
mDozeSensors.setProxListening(mWantProxSensor && lowPowerStateOrOff);
- mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, lowPowerStateOrOff);
+ mDozeSensors.setListeningWithPowerState(mWantSensors, mWantTouchScreenSensors,
+ mInAod, lowPowerStateOrOff);
if (mAodInterruptRunnable != null && state == Display.STATE_ON) {
mAodInterruptRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a5d9fee..22052d9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -531,6 +531,11 @@
@JvmField
val OUTPUT_SWITCHER_DEVICE_STATUS = unreleasedFlag(2502, "output_switcher_device_status")
+ // TODO(b/20911786): Tracking Bug
+ @JvmField
+ val OUTPUT_SWITCHER_SHOW_API_ENABLED =
+ unreleasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
+
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
new file mode 100644
index 0000000..e35575b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import android.annotation.MainThread;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.CommandQueue;
+
+import javax.inject.Inject;
+
+/** Controls display of media output switcher. */
+@SysUISingleton
+public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue.Callbacks {
+
+ private static final String TAG = "MediaOutputSwitcherDialogUI";
+
+ private final CommandQueue mCommandQueue;
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final FeatureFlags mFeatureFlags;
+
+ @Inject
+ public MediaOutputSwitcherDialogUI(
+ Context context,
+ CommandQueue commandQueue,
+ MediaOutputDialogFactory mediaOutputDialogFactory,
+ FeatureFlags featureFlags) {
+ mCommandQueue = commandQueue;
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mFeatureFlags = featureFlags;
+ }
+
+ @Override
+ public void start() {
+ if (mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_SHOW_API_ENABLED)) {
+ mCommandQueue.addCallback(this);
+ } else {
+ Log.w(TAG, "Show media output switcher is not enabled.");
+ }
+ }
+
+ @Override
+ @MainThread
+ public void showMediaOutputSwitcher(String packageName) {
+ if (!TextUtils.isEmpty(packageName)) {
+ mMediaOutputDialogFactory.create(packageName, false, null);
+ } else {
+ Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 245a55d..3e6eb05 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -525,6 +525,15 @@
}
private void updateIsEnabled() {
+ try {
+ Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
+ updateIsEnabledTraced();
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void updateIsEnabledTraced() {
boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
if (isEnabled == mIsEnabled) {
return;
@@ -611,14 +620,16 @@
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
- if (mEdgeBackPlugin != null) {
- mEdgeBackPlugin.onDestroy();
+ try {
+ Trace.beginSection("setEdgeBackPlugin");
+ mEdgeBackPlugin = edgeBackPlugin;
+ mEdgeBackPlugin.setBackCallback(mBackCallback);
+ mEdgeBackPlugin.setMotionEventsHandler(mMotionEventsHandler);
+ mEdgeBackPlugin.setLayoutParams(createLayoutParams());
+ updateDisplaySize();
+ } finally {
+ Trace.endSection();
}
- mEdgeBackPlugin = edgeBackPlugin;
- mEdgeBackPlugin.setBackCallback(mBackCallback);
- mEdgeBackPlugin.setMotionEventsHandler(mMotionEventsHandler);
- mEdgeBackPlugin.setLayoutParams(createLayoutParams());
- updateDisplaySize();
}
public boolean isHandlingGestures() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 04adaae..6e4ed7b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -43,12 +43,14 @@
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Pair;
import android.util.SparseArray;
@@ -166,6 +168,7 @@
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
+ private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -490,6 +493,11 @@
* @see IStatusBar#enterStageSplitFromRunningApp
*/
default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
+
+ /**
+ * @see IStatusBar#showMediaOutputSwitcher
+ */
+ default void showMediaOutputSwitcher(String packageName) {}
}
public CommandQueue(Context context) {
@@ -1259,6 +1267,19 @@
}
@Override
+ public void showMediaOutputSwitcher(String packageName) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("Call only allowed from system server.");
+ }
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = packageName;
+ mHandler.obtainMessage(MSG_SHOW_MEDIA_OUTPUT_SWITCHER, args).sendToTarget();
+ }
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@@ -1774,6 +1795,13 @@
mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
}
break;
+ case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
+ args = (SomeArgs) msg.obj;
+ String clientPackageName = (String) args.arg1;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 4b56594..ec08bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -295,6 +295,7 @@
private final Context mContext;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final DeviceStateManager mDeviceStateManager;
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private NotificationListContainer mNotifListContainer;
@@ -862,8 +863,7 @@
mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT,
id -> onLaunchTransitionTimeout());
- deviceStateManager.registerCallback(mMainExecutor,
- new FoldStateListener(mContext, this::onFoldedStateChanged));
+ mDeviceStateManager = deviceStateManager;
wiredChargingRippleController.registerCallbacks();
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
@@ -1052,6 +1052,8 @@
}
});
+ registerCallbacks();
+
mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
mPluginManager.addPluginListener(
@@ -1107,6 +1109,14 @@
}
@VisibleForTesting
+ /** Registers listeners/callbacks with external dependencies. */
+ void registerCallbacks() {
+ //TODO(b/264502026) move the rest of the listeners here.
+ mDeviceStateManager.registerCallback(mMainExecutor,
+ new FoldStateListener(mContext, this::onFoldedStateChanged));
+ }
+
+ @VisibleForTesting
void initShadeVisibilityListener() {
mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 7726d09..8214822 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -3,26 +3,43 @@
import android.os.SystemProperties
import android.os.VibrationEffect
import android.os.Vibrator
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
import javax.inject.Inject
-/**
- * Class that plays a haptics effect during unfolding a foldable device
- */
+/** Class that plays a haptics effect during unfolding a foldable device */
@SysUIUnfoldScope
class UnfoldHapticsPlayer
@Inject
constructor(
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ foldProvider: FoldProvider,
+ @Main private val mainExecutor: Executor,
private val vibrator: Vibrator?
) : TransitionProgressListener {
+ private var isFirstAnimationAfterUnfold = false
+
init {
if (vibrator != null) {
// We don't need to remove the callback because we should listen to it
// the whole time when SystemUI process is alive
unfoldTransitionProgressProvider.addCallback(this)
}
+
+ foldProvider.registerCallback(
+ object : FoldCallback {
+ override fun onFoldUpdated(isFolded: Boolean) {
+ if (isFolded) {
+ isFirstAnimationAfterUnfold = true
+ }
+ }
+ },
+ mainExecutor
+ )
}
private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
@@ -36,6 +53,13 @@
}
override fun onTransitionFinishing() {
+ // Run haptics only when unfolding the device (first animation after unfolding)
+ if (!isFirstAnimationAfterUnfold) {
+ return
+ }
+
+ isFirstAnimationAfterUnfold = false
+
// Run haptics only if the animation is long enough to notice
if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) {
playHaptics()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index cefba62..b6da649 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -146,7 +146,7 @@
@Test
public void testSensorDebounce() {
- mDozeSensors.setListening(true, true);
+ mDozeSensors.setListening(true, true, true);
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
@@ -164,7 +164,7 @@
@Test
public void testSetListening_firstTrue_registerSettingsObserver() {
verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
- mDozeSensors.setListening(true, true);
+ mDozeSensors.setListening(true, true, true);
verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
}
@@ -172,8 +172,8 @@
@Test
public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
- mDozeSensors.setListening(true, true);
- mDozeSensors.setListening(true, true);
+ mDozeSensors.setListening(true, true, true);
+ mDozeSensors.setListening(true, true, true);
verify(mTriggerSensor, times(1)).registerSettingsObserver(any(ContentObserver.class));
}
@@ -198,7 +198,7 @@
assertFalse(mSensorTap.mRequested);
// WHEN we're now in a low powered state
- dozeSensors.setListening(true, true, true);
+ dozeSensors.setListeningWithPowerState(true, true, true, true);
// THEN the tap sensor is registered
assertTrue(mSensorTap.mRequested);
@@ -209,12 +209,12 @@
// GIVEN doze sensors enabled
when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
- // GIVEN a trigger sensor
+ // GIVEN a trigger sensor that's enabled by settings
Sensor mockSensor = mock(Sensor.class);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
mockSensor,
- /* settingEnabled */ true,
- /* requiresTouchScreen */ true);
+ /* settingEnabled */ true
+ );
when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
.thenReturn(true);
@@ -230,12 +230,12 @@
// GIVEN doze sensors enabled
when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
- // GIVEN a trigger sensor
+ // GIVEN a trigger sensor that's not enabled by settings
Sensor mockSensor = mock(Sensor.class);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
mockSensor,
- /* settingEnabled*/ false,
- /* requiresTouchScreen */ true);
+ /* settingEnabled*/ false
+ );
when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
.thenReturn(true);
@@ -251,12 +251,12 @@
// GIVEN doze sensors enabled
when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
- // GIVEN a trigger sensor that's
+ // GIVEN a trigger sensor that's not enabled by settings
Sensor mockSensor = mock(Sensor.class);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
mockSensor,
- /* settingEnabled*/ false,
- /* requiresTouchScreen */ true);
+ /* settingEnabled*/ false
+ );
when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
.thenReturn(true);
@@ -266,7 +266,7 @@
// WHEN ignoreSetting is called
triggerSensor.ignoreSetting(true);
- // THEN the sensor is registered
+ // THEN the sensor is still registered since the setting is ignore
assertTrue(triggerSensor.mRegistered);
}
@@ -277,10 +277,10 @@
// GIVEN a trigger sensor
Sensor mockSensor = mock(Sensor.class);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
mockSensor,
- /* settingEnabled*/ true,
- /* requiresTouchScreen */ true);
+ /* settingEnabled*/ true
+ );
when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
.thenReturn(true);
@@ -297,7 +297,7 @@
// GIVEN doze sensor that supports postures
Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
new Sensor[] {
null /* unknown */,
closedSensor,
@@ -318,7 +318,7 @@
// GIVEN doze sensor that supports postures
Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
new Sensor[] {
null /* unknown */,
closedSensor,
@@ -347,7 +347,7 @@
// GIVEN doze sensor that supports postures
Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
new Sensor[] {
null /* unknown */,
closedSensor,
@@ -402,7 +402,7 @@
public void testUdfpsEnrollmentChanged() throws Exception {
// GIVEN a UDFPS_LONG_PRESS trigger sensor that's not configured
Sensor mockSensor = mock(Sensor.class);
- TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
mockSensor,
REASON_SENSOR_UDFPS_LONG_PRESS,
/* configured */ false);
@@ -411,7 +411,7 @@
.thenReturn(true);
// WHEN listening state is set to TRUE
- mDozeSensors.setListening(true, true);
+ mDozeSensors.setListening(true, true, true);
// THEN mRegistered is still false b/c !mConfigured
assertFalse(triggerSensor.mConfigured);
@@ -441,6 +441,35 @@
}
@Test
+ public void aodOnlySensor_onlyRegisteredWhenAodSensorsIncluded() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor that requires aod
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor aodOnlyTriggerSensor = mDozeSensors.createDozeSensorRequiringAod(mockSensor);
+ when(mSensorManager.requestTriggerSensor(eq(aodOnlyTriggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+ mDozeSensors.addSensor(aodOnlyTriggerSensor);
+
+ // WHEN aod only sensors aren't included
+ mDozeSensors.setListening(/* listen */ true, /* includeTouchScreenSensors */true,
+ /* includeAodOnlySensors */false);
+
+ // THEN the sensor is not registered or requested
+ assertFalse(aodOnlyTriggerSensor.mRequested);
+ assertFalse(aodOnlyTriggerSensor.mRegistered);
+
+ // WHEN aod only sensors ARE included
+ mDozeSensors.setListening(/* listen */ true, /* includeTouchScreenSensors */true,
+ /* includeAodOnlySensors */true);
+
+ // THEN the sensor is registered and requested
+ assertTrue(aodOnlyTriggerSensor.mRequested);
+ assertTrue(aodOnlyTriggerSensor.mRegistered);
+ }
+
+ @Test
public void liftToWake_defaultSetting_configDefaultFalse() {
// WHEN the default lift to wake gesture setting is false
when(mResources.getBoolean(
@@ -496,8 +525,8 @@
mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
- public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
- boolean requiresTouchScreen) {
+ public TriggerSensor createDozeSensorWithSettingEnabled(Sensor sensor,
+ boolean settingEnabled) {
return new TriggerSensor(/* sensor */ sensor,
/* setting name */ "test_setting",
/* settingDefault */ settingEnabled,
@@ -506,11 +535,13 @@
/* reportsTouchCoordinate*/ false,
/* requiresTouchscreen */ false,
/* ignoresSetting */ false,
- requiresTouchScreen,
- /* immediatelyReRegister */ true);
+ /* requiresProx */ false,
+ /* immediatelyReRegister */ true,
+ /* requiresAod */false
+ );
}
- public TriggerSensor createDozeSensor(
+ public TriggerSensor createDozeSensorForPosture(
Sensor sensor,
int pulseReason,
boolean configured
@@ -524,15 +555,35 @@
/* requiresTouchscreen */ false,
/* ignoresSetting */ false,
/* requiresTouchScreen */ false,
- /* immediatelyReRegister*/ true);
+ /* immediatelyReRegister*/ true,
+ false
+ );
}
/**
- * create a doze sensor that supports postures and is enabled
+ * Create a doze sensor that requires Aod
*/
- public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) {
+ public TriggerSensor createDozeSensorRequiringAod(Sensor sensor) {
+ return new TriggerSensor(/* sensor */ sensor,
+ /* setting name */ "aod_requiring_sensor",
+ /* settingDefault */ true,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ false,
+ /* requiresProx */ false,
+ /* immediatelyReRegister */ true,
+ /* requiresAoD */ true
+ );
+ }
+
+ /**
+ * Create a doze sensor that supports postures and is enabled
+ */
+ public TriggerSensor createDozeSensorForPosture(Sensor[] sensors, int posture) {
return new TriggerSensor(/* sensor */ sensors,
- /* setting name */ "test_setting",
+ /* setting name */ "posture_test_setting",
/* settingDefault */ true,
/* configured */ true,
/* pulseReason*/ 0,
@@ -541,7 +592,9 @@
/* ignoresSetting */ true,
/* requiresProx */ false,
/* immediatelyReRegister */ true,
- posture);
+ posture,
+ /* requiresUi */ false
+ );
}
public void addSensor(TriggerSensor sensor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index b66a454..3552399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -395,6 +395,14 @@
verify(mAuthController).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
}
+
+ @Test
+ public void udfpsLongPress_dozeState_notRegistered() {
+ // GIVEN device is DOZE_AOD_PAUSED
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ // beverlyt
+ }
+
private void waitForSensorManager() {
mExecutor.runAllReady();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c8157cc..4c1b219 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -544,6 +544,7 @@
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
notificationLogger.setUpWithContainer(mNotificationListContainer);
+ mCentralSurfaces.registerCallbacks();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
index c316402..4a28cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
@@ -26,6 +26,10 @@
listeners.forEach { it.onTransitionFinished() }
}
+ override fun onTransitionFinishing() {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
+
override fun onTransitionProgress(progress: Float) {
listeners.forEach { it.onTransitionProgress(progress) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
new file mode 100644
index 0000000..d3fdbd9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.unfold
+
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldHapticsPlayerTest : SysuiTestCase() {
+
+ private val progressProvider = TestUnfoldTransitionProvider()
+ private val vibrator: Vibrator = mock()
+ private val testFoldProvider = TestFoldProvider()
+
+ private lateinit var player: UnfoldHapticsPlayer
+
+ @Before
+ fun before() {
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator)
+ }
+
+ @Test
+ fun testUnfoldingTransitionFinishingEarly_playsHaptics() {
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+
+ verify(vibrator).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() {
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.99f)
+ progressProvider.onTransitionFinishing()
+
+ verify(vibrator, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun testFoldingAfterUnfolding_doesNotPlayHaptics() {
+ // Unfold
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+ progressProvider.onTransitionFinished()
+ clearInvocations(vibrator)
+
+ // Fold
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinished()
+ testFoldProvider.onFoldUpdate(isFolded = true)
+
+ verify(vibrator, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun testUnfoldingAfterFoldingAndUnfolding_playsHaptics() {
+ // Unfold
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+ progressProvider.onTransitionFinished()
+
+ // Fold
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinished()
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ clearInvocations(vibrator)
+
+ // Unfold again
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+ progressProvider.onTransitionFinished()
+
+ verify(vibrator).vibrate(any<VibrationEffect>())
+ }
+
+ private class TestFoldProvider : FoldProvider {
+ private val listeners = arrayListOf<FoldProvider.FoldCallback>()
+
+ override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) {
+ listeners += callback
+ }
+
+ override fun unregisterCallback(callback: FoldProvider.FoldCallback) {
+ listeners -= callback
+ }
+
+ fun onFoldUpdate(isFolded: Boolean) {
+ listeners.forEach { it.onFoldUpdated(isFolded) }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8c09dcd..dc475f6 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,7 +25,6 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
-import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -723,6 +722,17 @@
}
@Override
+ public void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId,
+ "setFrameworkSchedulingEnabledForUser()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.setFrameworkSchedulingEnabled(isEnabled);
+ }
+ }
+
+ @Override
public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
throws RemoteException {
if (isUserReadyForBackup(userId)) {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 0bb25e3..fe0e1c6 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -45,9 +45,12 @@
private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
public static void schedule(int userId, Context ctx, long minDelay,
- BackupManagerConstants constants) {
+ UserBackupManagerService userBackupManagerService) {
+ if (!userBackupManagerService.isFrameworkSchedulingEnabled()) return;
+
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
+ final BackupManagerConstants constants = userBackupManagerService.getConstants();
synchronized (constants) {
builder.setRequiresDeviceIdle(true)
.setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 058dcae..164bbea 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -64,14 +64,16 @@
@VisibleForTesting
public static final int MAX_JOB_ID = 52418896;
- public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
- schedule(userId, ctx, 0, constants);
+ public static void schedule(int userId, Context ctx,
+ UserBackupManagerService userBackupManagerService) {
+ schedule(userId, ctx, 0, userBackupManagerService);
}
public static void schedule(int userId, Context ctx, long delay,
- BackupManagerConstants constants) {
+ UserBackupManagerService userBackupManagerService) {
synchronized (KeyValueBackupJob.class) {
- if (sScheduledForUserId.get(userId)) {
+ if (sScheduledForUserId.get(userId)
+ || !userBackupManagerService.isFrameworkSchedulingEnabled()) {
return;
}
@@ -80,6 +82,7 @@
final int networkType;
final boolean needsCharging;
+ final BackupManagerConstants constants = userBackupManagerService.getConstants();
synchronized (constants) {
interval = constants.getKeyValueBackupIntervalMilliseconds();
fuzz = constants.getKeyValueBackupFuzzMilliseconds();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 6ba01d7..2c8bfeb 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1958,8 +1958,10 @@
}
// We don't want the backup jobs to kick in any time soon.
// Reschedules them to run in the distant future.
- KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
- FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS,
+ /* userBackupManagerService */ this);
+ FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS,
+ /* userBackupManagerService */ this);
} finally {
Binder.restoreCallingIdentity(oldToken);
}
@@ -2088,7 +2090,8 @@
final long interval = mConstants.getFullBackupIntervalMilliseconds();
final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0;
final long latency = Math.max(transportMinLatency, appLatency);
- FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, latency,
+ /* userBackupManagerService */ this);
} else {
if (DEBUG_SCHEDULING) {
Slog.i(
@@ -2226,7 +2229,8 @@
addUserIdToLogMessage(
mUserId, "Deferring scheduled full backups in battery saver mode"));
}
- FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval,
+ /* userBackupManagerService */ this);
return false;
}
@@ -2392,7 +2396,8 @@
+ "operation; rescheduling +" + latency));
}
final long deferTime = latency; // pin for the closure
- FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, deferTime,
+ /* userBackupManagerService */ this);
return false;
}
@@ -2495,7 +2500,8 @@
}
// ...and schedule a backup pass if necessary
- KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext,
+ /* userBackupManagerService */ this);
}
// Note: packageName is currently unused, but may be in the future
@@ -2730,7 +2736,8 @@
mUserId, "Not running backup while in battery save mode"));
}
// Try again in several hours.
- KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext,
+ /* userBackupManagerService */ this);
} else {
if (DEBUG) {
Slog.v(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass"));
@@ -3208,12 +3215,51 @@
}
}
+ synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setFrameworkSchedulingEnabled");
+
+ boolean wasEnabled = isFrameworkSchedulingEnabled();
+ if (wasEnabled == isEnabled) {
+ return;
+ }
+
+ Slog.i(TAG, addUserIdToLogMessage(mUserId,
+ (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"));
+
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ // TODO(b/264889098): Consider at a later point if we should us a sentinel file as
+ // setBackupEnabled.
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId);
+
+ if (!isEnabled) {
+ KeyValueBackupJob.cancel(mUserId, mContext);
+ FullBackupJob.cancel(mUserId, mContext);
+ } else {
+ KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this);
+ scheduleNextFullBackupJob(/* transportMinLatency */ 0);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ synchronized boolean isFrameworkSchedulingEnabled() {
+ // By default scheduling is enabled
+ final int defaultSetting = 1;
+ int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId);
+ return isEnabled == 1;
+ }
+
@VisibleForTesting
void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {
synchronized (mQueueLock) {
if (enable && !wasEnabled && mSetupComplete) {
// if we've just been enabled, start scheduling backup passes
- KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this);
scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
@@ -4127,6 +4173,8 @@
pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
if (mBackupRunning) pw.println("Backup currently running");
pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
+ pw.println("Framework scheduling is "
+ + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled"));
pw.println("Last backup pass started: " + mLastBackupPass
+ " (now = " + System.currentTimeMillis() + ')');
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index c5e912e..f399fe9 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
-import android.provider.Settings;
import android.util.Slog;
import com.android.server.backup.KeyValueBackupJob;
@@ -78,7 +77,7 @@
Slog.d(TAG, "Setup complete so starting backups");
}
KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
- mUserBackupManagerService.getConstants());
+ mUserBackupManagerService);
mUserBackupManagerService.scheduleNextFullBackupJob(0);
}
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index ca92b69..41e8092 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1246,7 +1246,7 @@
delay = 0;
}
KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
- mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
+ mBackupManagerService.getContext(), delay, mBackupManagerService);
for (String packageName : mOriginalQueue) {
mBackupManagerService.dataChangedImpl(packageName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9752e8b..8e1f969 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17364,6 +17364,11 @@
}
@Override
+ public Pair<Integer, Integer> getCurrentAndTargetUserIds() {
+ return mUserController.getCurrentAndTargetUserIds();
+ }
+
+ @Override
public int getCurrentUserId() {
return mUserController.getCurrentUserId();
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 234eec3..f61737e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2742,6 +2742,12 @@
return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
}
+ Pair<Integer, Integer> getCurrentAndTargetUserIds() {
+ synchronized (mLock) {
+ return new Pair<>(mCurrentUserId, mTargetUserId);
+ }
+ }
+
@GuardedBy("mLock")
private int getCurrentUserIdLU() {
return mCurrentUserId;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index cde4ea9..ffc309e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -64,7 +64,6 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -93,8 +92,6 @@
// in MediaRouter2, remove this constant and replace the usages with the real request IDs.
private static final long DUMMY_REQUEST_ID = -1;
- private static final int DUMP_EVENTS_MAX_COUNT = 70;
-
private static final String MEDIA_BETTER_TOGETHER_NAMESPACE = "media_better_together";
private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
@@ -121,9 +118,6 @@
@GuardedBy("mLock")
private int mCurrentActiveUserId = -1;
- private final EventLogger mEventLogger =
- new EventLogger(DUMP_EVENTS_MAX_COUNT, "MediaRouter2ServiceImpl");
-
private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
(uid, importance) -> {
synchronized (mLock) {
@@ -689,16 +683,14 @@
} else {
pw.println(indent + " <no user records>");
}
- mEventLogger.dump(pw, indent);
}
}
/* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
synchronized (mLock) {
if (mCurrentActiveUserId != newActiveUserId) {
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("switchUser",
- "userId: %d", newActiveUserId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "switchUser | user: %d", newActiveUserId));
mCurrentActiveUserId = newActiveUserId;
// disposeUserIfNeededLocked might modify the collection, hence clone
@@ -771,8 +763,8 @@
obtainMessage(UserHandler::notifyRouterRegistered,
userRecord.mHandler, routerRecord));
- mEventLogger.enqueue(EventLogger.StringEvent.from("registerRouter2",
- "package: %s, uid: %d, pid: %d, router id: %d",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "registerRouter2 | package: %s, uid: %d, pid: %d, router: %d",
packageName, uid, pid, routerRecord.mRouterId));
}
@@ -784,12 +776,10 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from(
- "unregisterRouter2",
- "package: %s, router id: %d",
- routerRecord.mPackageName,
- routerRecord.mRouterId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "unregisterRouter2 | package: %s, router: %d",
+ routerRecord.mPackageName,
+ routerRecord.mRouterId));
UserRecord userRecord = routerRecord.mUserRecord;
userRecord.mRouterRecords.remove(routerRecord);
@@ -816,9 +806,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "setDiscoveryRequestWithRouter2",
- "router id: %d, discovery request: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setDiscoveryRequestWithRouter2 | router: %d, discovery request: %s",
routerRecord.mRouterId, discoveryRequest.toString()));
routerRecord.mDiscoveryPreference = discoveryRequest;
@@ -842,12 +831,11 @@
.map(RouteListingPreference.Item::getRouteId)
.collect(Collectors.joining(","))
: null;
- mEventLogger.enqueue(
- EventLogger.StringEvent.from(
- "setRouteListingPreference",
- "router id: %d, route listing preference: [%s]",
- routerRecord.mRouterId,
- routeListingAsString));
+
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setRouteListingPreference | router: %d, route listing preference: [%s]",
+ routerRecord.mRouterId,
+ routeListingAsString));
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(
@@ -863,9 +851,8 @@
RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord != null) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "setRouteVolumeWithRouter2",
- "router id: %d, volume: %d",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setRouteVolumeWithRouter2 | router: %d, volume: %d",
routerRecord.mRouterId, volume));
routerRecord.mUserRecord.mHandler.sendMessage(
@@ -948,9 +935,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "selectRouteWithRouter2",
- "router id: %d, route: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "selectRouteWithRouter2 | router: %d, route: %s",
routerRecord.mRouterId, route.getId()));
routerRecord.mUserRecord.mHandler.sendMessage(
@@ -968,9 +954,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "deselectRouteWithRouter2",
- "router id: %d, route: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "deselectRouteWithRouter2 | router: %d, route: %s",
routerRecord.mRouterId, route.getId()));
routerRecord.mUserRecord.mHandler.sendMessage(
@@ -988,9 +973,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "transferToRouteWithRouter2",
- "router id: %d, route: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "transferToRouteWithRouter2 | router: %d, route: %s",
routerRecord.mRouterId, route.getId()));
String defaultRouteId =
@@ -1018,9 +1002,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "setSessionVolumeWithRouter2",
- "router id: %d, session: %s, volume: %d",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setSessionVolumeWithRouter2 | router: %d, session: %s, volume: %d",
routerRecord.mRouterId, uniqueSessionId, volume));
routerRecord.mUserRecord.mHandler.sendMessage(
@@ -1038,9 +1021,8 @@
return;
}
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "releaseSessionWithRouter2",
- "router id: %d, session: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "releaseSessionWithRouter2 | router: %d, session: %s",
routerRecord.mRouterId, uniqueSessionId));
routerRecord.mUserRecord.mHandler.sendMessage(
@@ -1084,10 +1066,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("registerManager",
- "uid: %d, pid: %d, package: %s, userId: %d",
- uid, pid, packageName, userId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "registerManager | uid: %d, pid: %d, package: %s, user: %d",
+ uid, pid, packageName, userId));
mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
"Must hold MEDIA_CONTENT_CONTROL permission.");
@@ -1135,13 +1116,11 @@
}
UserRecord userRecord = managerRecord.mUserRecord;
- mEventLogger.enqueue(
- EventLogger.StringEvent.from(
- "unregisterManager",
- "package: %s, userId: %d, managerId: %d",
- managerRecord.mPackageName,
- userRecord.mUserId,
- managerRecord.mManagerId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "unregisterManager | package: %s, user: %d, manager: %d",
+ managerRecord.mPackageName,
+ userRecord.mUserId,
+ managerRecord.mManagerId));
userRecord.mManagerRecords.remove(managerRecord);
managerRecord.dispose();
@@ -1155,9 +1134,8 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("startScan",
- "manager: %d", managerRecord.mManagerId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "startScan | manager: %d", managerRecord.mManagerId));
managerRecord.startScan();
}
@@ -1169,9 +1147,8 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("stopScan",
- "manager: %d", managerRecord.mManagerId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "stopScan | manager: %d", managerRecord.mManagerId));
managerRecord.stopScan();
}
@@ -1186,10 +1163,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("setRouteVolumeWithManager",
- "managerId: %d, routeId: %s, volume: %d",
- managerRecord.mManagerId, route.getId(), volume));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setRouteVolumeWithManager | manager: %d, route: %s, volume: %d",
+ managerRecord.mManagerId, route.getId(), volume));
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -1206,10 +1182,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("requestCreateSessionWithManager",
- "managerId: %d, routeId: %s",
- managerRecord.mManagerId, route.getId()));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "requestCreateSessionWithManager | manager: %d, route: %s",
+ managerRecord.mManagerId, route.getId()));
String packageName = oldSession.getClientPackageName();
@@ -1256,10 +1231,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("selectRouteWithManager",
- "managerId: %d, session: %s, routeId: %s",
- managerRecord.mManagerId, uniqueSessionId, route.getId()));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "selectRouteWithManager | manager: %d, session: %s, route: %s",
+ managerRecord.mManagerId, uniqueSessionId, route.getId()));
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
@@ -1282,10 +1256,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("deselectRouteWithManager",
- "managerId: %d, session: %s, routeId: %s",
- managerRecord.mManagerId, uniqueSessionId, route.getId()));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "deselectRouteWithManager | manager: %d, session: %s, route: %s",
+ managerRecord.mManagerId, uniqueSessionId, route.getId()));
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
@@ -1308,10 +1281,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("transferToRouteWithManager",
- "managerId: %d, session: %s, routeId: %s",
- managerRecord.mManagerId, uniqueSessionId, route.getId()));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "transferToRouteWithManager | manager: %d, session: %s, route: %s",
+ managerRecord.mManagerId, uniqueSessionId, route.getId()));
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
@@ -1334,10 +1306,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("setSessionVolumeWithManager",
- "managerId: %d, session: %s, volume: %d",
- managerRecord.mManagerId, uniqueSessionId, volume));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "setSessionVolumeWithManager | manager: %d, session: %s, volume: %d",
+ managerRecord.mManagerId, uniqueSessionId, volume));
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -1355,10 +1326,9 @@
return;
}
- mEventLogger.enqueue(
- EventLogger.StringEvent.from("releaseSessionWithManager",
- "managerId: %d, session: %s",
- managerRecord.mManagerId, uniqueSessionId));
+ Slog.i(TAG, TextUtils.formatSimple(
+ "releaseSessionWithManager | manager: %d, session: %s",
+ managerRecord.mManagerId, uniqueSessionId));
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterWithSessionLocked(uniqueSessionId);
@@ -1791,8 +1761,7 @@
MediaRoute2ProviderInfo oldInfo =
providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
MediaRouter2ServiceImpl mediaRouter2Service = mServiceRef.get();
- EventLogger eventLogger =
- mediaRouter2Service != null ? mediaRouter2Service.mEventLogger : null;
+
if (oldInfo == newInfo) {
// Nothing to do.
return;
@@ -1854,23 +1823,21 @@
}
}
- if (eventLogger != null) {
- if (!addedRoutes.isEmpty()) {
- // If routes were added, newInfo cannot be null.
- eventLogger.enqueue(
- toLoggingEvent(
- /* source= */ "addProviderRoutes",
- newInfo.getUniqueId(),
- addedRoutes));
- }
- if (!removedRoutes.isEmpty()) {
- // If routes were removed, oldInfo cannot be null.
- eventLogger.enqueue(
- toLoggingEvent(
- /* source= */ "removeProviderRoutes",
- oldInfo.getUniqueId(),
- removedRoutes));
- }
+ if (!addedRoutes.isEmpty()) {
+ // If routes were added, newInfo cannot be null.
+ Slog.i(TAG,
+ toLoggingMessage(
+ /* source= */ "addProviderRoutes",
+ newInfo.getUniqueId(),
+ addedRoutes));
+ }
+ if (!removedRoutes.isEmpty()) {
+ // If routes were removed, oldInfo cannot be null.
+ Slog.i(TAG,
+ toLoggingMessage(
+ /* source= */ "removeProviderRoutes",
+ oldInfo.getUniqueId(),
+ removedRoutes));
}
dispatchUpdates(
@@ -1880,14 +1847,14 @@
mSystemProvider.getDefaultRoute());
}
- private static EventLogger.Event toLoggingEvent(
+ private static String toLoggingMessage(
String source, String providerId, ArrayList<MediaRoute2Info> routes) {
String routesString =
routes.stream()
.map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
.collect(Collectors.joining(/* delimiter= */ ", "));
- return EventLogger.StringEvent.from(
- source, "provider: %s, routes: [%s]", providerId, routesString);
+ return TextUtils.formatSimple("%s | provider: %s, routes: [%s]",
+ source, providerId, routesString);
}
/**
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 38efc10..d4e3549 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -255,6 +255,12 @@
@Override
@Deprecated
+ public final void clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+ mPreferredActivityHelper.clearPersistentPreferredActivity(filter, userId);
+ }
+
+ @Override
+ @Deprecated
public final void clearPackagePreferredActivities(String packageName) {
mPreferredActivityHelper.clearPackagePreferredActivities(snapshot(),
packageName);
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 6f8995c..214a8b8 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -431,6 +431,23 @@
}
}
+ public void clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "clearPersistentPreferredActivity can only be run by the system");
+ }
+ boolean changed = false;
+ synchronized (mPm.mLock) {
+ changed = mPm.mSettings.clearPersistentPreferredActivity(filter, userId);
+ }
+ if (changed) {
+ updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
+ mPm.postPreferredActivityChangedBroadcast(userId);
+ mPm.scheduleWritePackageRestrictions(userId);
+ }
+ }
+
private boolean isHomeFilter(@NonNull WatchedIntentFilter filter) {
return filter.hasAction(Intent.ACTION_MAIN) && filter.hasCategory(Intent.CATEGORY_HOME)
&& filter.hasCategory(CATEGORY_DEFAULT);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aedf782..920fafd 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -99,6 +99,7 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.permission.persistence.RuntimePermissionsState;
+import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
@@ -6278,6 +6279,24 @@
return changed;
}
+ boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+ PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
+ Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
+ boolean changed = false;
+ while (it.hasNext()) {
+ PersistentPreferredActivity ppa = it.next();
+ if (IntentResolver.filterEquals(ppa.getIntentFilter(), filter)) {
+ ppir.removeFilter(ppa);
+ changed = true;
+ break;
+ }
+ }
+ if (changed) {
+ onChanged();
+ }
+ return changed;
+ }
+
ArrayList<Integer> systemReady(ComponentResolver resolver) {
// Verify that all of the preferred activity components actually
// exist. It is possible for applications to be updated and at
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 175c11d..372d0aa 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -101,6 +101,7 @@
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -1835,6 +1836,27 @@
}
/**
+ * Gets the current and target user ids as a {@link Pair}, calling
+ * {@link ActivityManagerInternal} directly (and without performing any permission check).
+ *
+ * @return ids of current foreground user and the target user. Target user will be
+ * {@link UserHandle#USER_NULL} if there is not an ongoing user switch. And if
+ * {@link ActivityManagerInternal} is not available yet, they will both be
+ * {@link UserHandle#USER_NULL}.
+ */
+ @VisibleForTesting
+ @NonNull
+ Pair<Integer, Integer> getCurrentAndTargetUserIds() {
+ ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
+ if (activityManagerInternal == null) {
+ Slog.w(LOG_TAG, "getCurrentAndTargetUserId() called too early, "
+ + "ActivityManagerInternal is not set yet");
+ return new Pair<>(UserHandle.USER_NULL, UserHandle.USER_NULL);
+ }
+ return activityManagerInternal.getCurrentAndTargetUserIds();
+ }
+
+ /**
* Gets the current user id, calling {@link ActivityManagerInternal} directly (and without
* performing any permission check).
*
@@ -5426,11 +5448,15 @@
final long ident = Binder.clearCallingIdentity();
try {
final UserData userData;
- int currentUser = getCurrentUserId();
- if (currentUser == userId) {
+ Pair<Integer, Integer> currentAndTargetUserIds = getCurrentAndTargetUserIds();
+ if (userId == currentAndTargetUserIds.first) {
Slog.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
+ if (userId == currentAndTargetUserIds.second) {
+ Slog.w(LOG_TAG, "Target user of an ongoing user switch cannot be removed.");
+ return false;
+ }
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
userData = mUsers.get(userId);
@@ -5567,9 +5593,10 @@
}
}
- // Attempt to immediately remove a non-current user
- final int currentUser = getCurrentUserId();
- if (currentUser != userId) {
+ // Attempt to immediately remove a non-current and non-target user
+ Pair<Integer, Integer> currentAndTargetUserIds = getCurrentAndTargetUserIds();
+ if (userId != currentAndTargetUserIds.first
+ && userId != currentAndTargetUserIds.second) {
// Attempt to remove the user. This will fail if the user is the current user
if (removeUserWithProfilesUnchecked(userId)) {
return UserManager.REMOVE_RESULT_REMOVED;
@@ -5578,9 +5605,14 @@
// If the user was not immediately removed, make sure it is marked as ephemeral.
// Don't mark as disabled since, per UserInfo.FLAG_DISABLED documentation, an
// ephemeral user should only be marked as disabled when its removal is in progress.
- Slog.i(LOG_TAG, "Unable to immediately remove user " + userId + " (current user is "
- + currentUser + "). User is set as ephemeral and will be removed on user "
- + "switch or reboot.");
+ Slog.i(LOG_TAG, TextUtils.formatSimple("Unable to immediately remove user %d "
+ + "(%s is %d). User is set as ephemeral and will be removed on "
+ + "user switch or reboot.",
+ userId,
+ userId == currentAndTargetUserIds.first
+ ? "current user"
+ : "target user of an ongoing user switch",
+ userId));
userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
writeUserLP(userData);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0fd6d9b..05b3ce7 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -217,4 +217,12 @@
* @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
*/
void enterStageSplitFromRunningApp(boolean leftOrTop);
+
+ /**
+ * Shows the media output switcher dialog.
+ *
+ * @param packageName of the session for which the output switcher is shown.
+ * @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
+ */
+ void showMediaOutputSwitcher(String packageName);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 97ca8df..0f49981 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -744,6 +744,16 @@
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void showMediaOutputSwitcher(String packageName) {
+ if (mBar != null) {
+ try {
+ mBar.showMediaOutputSwitcher(packageName);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0d6c8db..63d6509 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3030,7 +3030,7 @@
if (density == getInitialDisplayDensity()) {
density = 0;
}
- mWmService.mDisplayWindowSettings.setForcedDensity(this, density, userId);
+ mWmService.mDisplayWindowSettings.setForcedDensity(getDisplayInfo(), density, userId);
}
/** @param mode {@link #FORCE_SCALING_MODE_AUTO} or {@link #FORCE_SCALING_MODE_DISABLED}. */
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 6d47eeb..4c0435e 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -77,14 +77,14 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setForcedDensity(DisplayContent displayContent, int density, int userId) {
- if (displayContent.isDefaultDisplay) {
+ void setForcedDensity(DisplayInfo info, int density, int userId) {
+ if (info.displayId == Display.DEFAULT_DISPLAY) {
final String densityString = density == 0 ? "" : Integer.toString(density);
Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
}
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final DisplayInfo displayInfo = info;
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
overrideSettings.mForcedDensity = density;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5adae41..18ad43c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5855,6 +5855,11 @@
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
return displayContent.getInitialDisplayDensity();
}
+
+ DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (info != null && info.hasAccess(Binder.getCallingUid())) {
+ return info.logicalDensityDpi;
+ }
}
return -1;
}
@@ -5901,6 +5906,11 @@
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
displayContent.setForcedDensity(density, targetUserId);
+ } else {
+ DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (info != null) {
+ mDisplayWindowSettings.setForcedDensity(info, density, userId);
+ }
}
}
} finally {
@@ -5925,6 +5935,12 @@
if (displayContent != null) {
displayContent.setForcedDensity(displayContent.getInitialDisplayDensity(),
callingUserId);
+ } else {
+ DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (info != null) {
+ mDisplayWindowSettings.setForcedDensity(info, info.logicalDensityDpi,
+ userId);
+ }
}
}
} finally {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
new file mode 100644
index 0000000..d400000
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName> {
+ private static final String ATTR_PACKAGE_NAME = ":package-name";
+ private static final String ATTR_CLASS_NAME = ":class-name";
+
+ @Override
+ void saveToXml(
+ TypedXmlSerializer serializer, String attributeNamePrefix, @NonNull ComponentName value)
+ throws IOException {
+ Objects.requireNonNull(value);
+ serializer.attribute(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_PACKAGE_NAME, value.getPackageName());
+ serializer.attribute(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_CLASS_NAME, value.getClassName());
+ }
+
+ @Nullable
+ @Override
+ ComponentName readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+ String packageName = parser.getAttributeValue(
+ /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME);
+ String className = parser.getAttributeValue(
+ /* namespace= */ null, attributeNamePrefix + ATTR_CLASS_NAME);
+ if (packageName == null || className == null) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy.");
+ return null;
+ }
+ return new ComponentName(packageName, className);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java
new file mode 100644
index 0000000..ab0fc99
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+/**
+ * Default implementation for {@link PolicyKey} used to identify a policy that doesn't require any
+ * additional arguments to be represented in the policy engine's data structure.
+ */
+final class DefaultPolicyKey extends PolicyKey {
+ private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key";
+
+ DefaultPolicyKey(String policyKey) {
+ super(policyKey);
+ }
+
+ String getKey() {
+ return mKey;
+ }
+
+ static DefaultPolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) {
+ String genericPolicyKey = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_GENERIC_POLICY_KEY);
+ return new DefaultPolicyKey(genericPolicyKey);
+ }
+
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 7ec809f..cb3b021 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,14 +16,10 @@
package com.android.server.devicepolicy;
-import static android.app.admin.PolicyUpdateReason.REASON_CONFLICTING_ADMIN_POLICY;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_SET_RESULT_KEY;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdateResult.RESULT_SUCCESS;
import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_REASON_KEY;
-import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_FAILURE;
-import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_SUCCESS;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
import android.Manifest;
import android.annotation.NonNull;
@@ -82,12 +78,12 @@
/**
* Map of <userId, Map<policyKey, policyState>>
*/
- private final SparseArray<Map<String, PolicyState<?>>> mLocalPolicies;
+ private final SparseArray<Map<PolicyKey, PolicyState<?>>> mLocalPolicies;
/**
* Map of <policyKey, policyState>
*/
- private final Map<String, PolicyState<?>> mGlobalPolicies;
+ private final Map<PolicyKey, PolicyState<?>> mGlobalPolicies;
/**
* Map containing the current set of admins in each user with active policies.
@@ -146,9 +142,8 @@
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
- policyEnforced,
// TODO: we're always sending this for now, should properly handle errors.
- REASON_CONFLICTING_ADMIN_POLICY,
+ policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
userId);
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
@@ -194,9 +189,8 @@
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
- policyEnforced,
// TODO: we're always sending this for now, should properly handle errors.
- REASON_CONFLICTING_ADMIN_POLICY,
+ policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
userId);
if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) {
@@ -223,7 +217,7 @@
// Send policy updates to admins who've set it locally
sendPolicyChangedToAdmins(
- localPolicyState.getPoliciesSetByAdmins().keySet(),
+ localPolicyState,
enforcingAdmin,
policyDefinition,
// This policy change is only relevant to a single user, not the global
@@ -234,7 +228,7 @@
if (hasGlobalPolicyLocked(policyDefinition)) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
sendPolicyChangedToAdmins(
- globalPolicyState.getPoliciesSetByAdmins().keySet(),
+ globalPolicyState,
enforcingAdmin,
policyDefinition,
userId);
@@ -266,13 +260,13 @@
policyDefinition, enforcingAdmin, value);
boolean policyEnforcedGlobally = Objects.equals(
globalPolicyState.getCurrentResolvedPolicy(), value);
+ boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers;
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
- policyEnforcedGlobally && policyEnforcedOnAllUsers,
// TODO: we're always sending this for now, should properly handle errors.
- REASON_CONFLICTING_ADMIN_POLICY,
+ policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
UserHandle.USER_ALL);
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
@@ -305,13 +299,13 @@
policyDefinition, enforcingAdmin, /* value= */ null);
// For a removePolicy to be enforced, it means no current policy exists
boolean policyEnforcedGlobally = policyState.getCurrentResolvedPolicy() == null;
+ boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers;
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
- policyEnforcedGlobally && policyEnforcedOnAllUsers,
// TODO: we're always sending this for now, should properly handle errors.
- REASON_CONFLICTING_ADMIN_POLICY,
+ policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
UserHandle.USER_ALL);
if (policyState.getPoliciesSetByAdmins().isEmpty()) {
@@ -336,7 +330,7 @@
UserHandle.USER_ALL);
sendPolicyChangedToAdmins(
- policyState.getPoliciesSetByAdmins().keySet(),
+ policyState,
enforcingAdmin,
policyDefinition,
UserHandle.USER_ALL);
@@ -376,7 +370,7 @@
enforcePolicy(
policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
sendPolicyChangedToAdmins(
- localPolicyState.getPoliciesSetByAdmins().keySet(),
+ localPolicyState,
enforcingAdmin,
policyDefinition,
// Even though this is caused by a global policy change, admins who've set
@@ -430,6 +424,42 @@
}
}
+ /**
+ * Returns the policies set by the given admin that share the same {@link PolicyKey#getKey()} as
+ * the provided {@code policyDefinition}.
+ *
+ * <p>For example, getLocalPolicyKeysSetByAdmin(PERMISSION_GRANT, admin) returns all permission
+ * grants set by the given admin.
+ *
+ * <p>Note that this will always return at most one item for policies that do not require
+ * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs
+ * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}).
+ *
+ */
+ @NonNull
+ <V> Set<PolicyKey> getLocalPolicyKeysSetByAdmin(
+ @NonNull PolicyDefinition<V> policyDefinition,
+ @NonNull EnforcingAdmin enforcingAdmin,
+ int userId) {
+ Objects.requireNonNull(policyDefinition);
+ Objects.requireNonNull(enforcingAdmin);
+
+ synchronized (mLock) {
+ if (policyDefinition.isGlobalOnlyPolicy() || !mLocalPolicies.contains(userId)) {
+ return Set.of();
+ }
+ Set<PolicyKey> keys = new HashSet<>();
+ for (PolicyKey key : mLocalPolicies.get(userId).keySet()) {
+ if (key.hasSameKeyAs(policyDefinition.getPolicyKey())
+ && mLocalPolicies.get(userId).get(key).getPoliciesSetByAdmins()
+ .containsKey(enforcingAdmin)) {
+ keys.add(key);
+ }
+ }
+ return keys;
+ }
+ }
+
private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) {
if (policyDefinition.isGlobalOnlyPolicy()) {
return false;
@@ -501,7 +531,7 @@
}
private static <V> PolicyState<V> getPolicyState(
- Map<String, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
+ Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
try {
// This will not throw an exception because policyDefinition is of type V, so unless
// we've created two policies with the same key but different types - we can only have
@@ -523,8 +553,7 @@
}
private <V> void sendPolicyResultToAdmin(
- EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, boolean success,
- int reason, int userId) {
+ EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) {
Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
intent.setPackage(admin.getPackageName());
@@ -539,21 +568,14 @@
}
Bundle extras = new Bundle();
- extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
- if (policyDefinition.getCallbackArgs() != null
- && !policyDefinition.getCallbackArgs().isEmpty()) {
- extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
- }
+ policyDefinition.getPolicyKey().writeToBundle(extras);
extras.putInt(
EXTRA_POLICY_TARGET_USER_ID,
getTargetUser(admin.getUserId(), userId));
extras.putInt(
- EXTRA_POLICY_SET_RESULT_KEY,
- success ? POLICY_SET_RESULT_SUCCESS : POLICY_SET_RESULT_FAILURE);
+ EXTRA_POLICY_UPDATE_RESULT_KEY,
+ result);
- if (!success) {
- extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
- }
intent.putExtras(extras);
maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
@@ -561,17 +583,21 @@
// TODO(b/261430877): Finalise the decision on which admins to send the updates to.
private <V> void sendPolicyChangedToAdmins(
- Set<EnforcingAdmin> admins,
+ PolicyState<V> policyState,
EnforcingAdmin callingAdmin,
PolicyDefinition<V> policyDefinition,
int userId) {
- for (EnforcingAdmin admin: admins) {
+ for (EnforcingAdmin admin: policyState.getPoliciesSetByAdmins().keySet()) {
// We're sending a separate broadcast for the calling admin with the result.
if (admin.equals(callingAdmin)) {
continue;
}
+ int result = Objects.equals(
+ policyState.getPoliciesSetByAdmins().get(admin),
+ policyState.getCurrentResolvedPolicy())
+ ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
maybeSendOnPolicyChanged(
- admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, userId);
+ admin, policyDefinition, result, userId);
}
}
@@ -592,15 +618,11 @@
}
Bundle extras = new Bundle();
- extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
- if (policyDefinition.getCallbackArgs() != null
- && !policyDefinition.getCallbackArgs().isEmpty()) {
- extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
- }
+ policyDefinition.getPolicyKey().writeToBundle(extras);
extras.putInt(
EXTRA_POLICY_TARGET_USER_ID,
getTargetUser(admin.getUserId(), userId));
- extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
+ extras.putInt(EXTRA_POLICY_UPDATE_RESULT_KEY, reason);
intent.putExtras(extras);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -779,14 +801,14 @@
}
private boolean doesAdminHavePolicies(@NonNull EnforcingAdmin enforcingAdmin) {
- for (String policy : mGlobalPolicies.keySet()) {
+ for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
return true;
}
}
for (int i = 0; i < mLocalPolicies.size(); i++) {
- for (String policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) {
+ for (PolicyKey policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) {
PolicyState<?> policyState = mLocalPolicies.get(
mLocalPolicies.keyAt(i)).get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
@@ -880,13 +902,12 @@
if (mLocalPolicies != null) {
for (int i = 0; i < mLocalPolicies.size(); i++) {
int userId = mLocalPolicies.keyAt(i);
- for (Map.Entry<String, PolicyState<?>> policy : mLocalPolicies.get(
+ for (Map.Entry<PolicyKey, PolicyState<?>> policy : mLocalPolicies.get(
userId).entrySet()) {
serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
- serializer.attribute(
- /* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+ policy.getKey().saveToXml(serializer);
serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
policy.getValue().saveToXml(serializer);
@@ -900,10 +921,10 @@
private void writeGlobalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
if (mGlobalPolicies != null) {
- for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
+ for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
- serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+ policy.getKey().saveToXml(serializer);
serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
policy.getValue().saveToXml(serializer);
@@ -973,8 +994,7 @@
private void readLocalPoliciesInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
- String policyKey = parser.getAttributeValue(
- /* namespace= */ null, ATTR_POLICY_ID);
+ PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
if (!mLocalPolicies.contains(userId)) {
mLocalPolicies.put(userId, new HashMap<>());
}
@@ -989,7 +1009,7 @@
private void readGlobalPoliciesInner(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
+ PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
if (adminsPolicy != null) {
mGlobalPolicies.put(policyKey, adminsPolicy);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fa6fa53..ce67f3c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10213,16 +10213,24 @@
|| isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
- synchronized (getLockObject()) {
- long id = mInjector.binderClearCallingIdentity();
- try {
- mIPackageManager.addPersistentPreferredActivity(filter, activity, userHandle);
- mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
- } catch (RemoteException re) {
- // Shouldn't happen
- Slog.wtf(LOG_TAG, "Error adding persistent preferred activity", re);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
+ if (isCoexistenceEnabled(caller)) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
+ activity,
+ userHandle);
+ } else {
+ synchronized (getLockObject()) {
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ mIPackageManager.addPersistentPreferredActivity(filter, activity, userHandle);
+ mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ Slog.wtf(LOG_TAG, "Error adding persistent preferred activity", re);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
}
}
final String activityPackage =
@@ -10242,17 +10250,61 @@
|| isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
- synchronized (getLockObject()) {
- long id = mInjector.binderClearCallingIdentity();
- try {
- mIPackageManager.clearPackagePersistentPreferredActivities(packageName, userHandle);
- mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
- } catch (RemoteException re) {
- // Shouldn't happen
- Slogf.wtf(
- LOG_TAG, "Error when clearing package persistent preferred activities", re);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
+
+ if (isCoexistenceEnabled(caller)) {
+ clearPackagePersistentPreferredActivitiesFromPolicyEngine(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
+ packageName,
+ userHandle);
+ } else {
+ synchronized (getLockObject()) {
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ mIPackageManager.clearPackagePersistentPreferredActivities(packageName,
+ userHandle);
+ mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ Slogf.wtf(
+ LOG_TAG, "Error when clearing package persistent preferred activities",
+ re);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove all persistent intent handler preferences associated with the given package that were
+ * set by this admin, note that is doesn't remove preferences set by other admins for the same
+ * package.
+ */
+ private void clearPackagePersistentPreferredActivitiesFromPolicyEngine(
+ EnforcingAdmin admin, String packageName, int userId) {
+ Set<PolicyKey> keys = mDevicePolicyEngine.getLocalPolicyKeysSetByAdmin(
+ PolicyDefinition.GENERIC_PERSISTENT_PREFERRED_ACTIVITY,
+ admin,
+ userId);
+ for (PolicyKey key : keys) {
+ if (!(key instanceof PersistentPreferredActivityPolicyKey)) {
+ throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not"
+ + "of type PersistentPreferredActivityPolicyKey");
+ }
+ PersistentPreferredActivityPolicyKey parsedKey =
+ (PersistentPreferredActivityPolicyKey) key;
+ IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter());
+
+ ComponentName preferredActivity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
+ admin,
+ userId);
+ if (preferredActivity != null
+ && preferredActivity.getPackageName().equals(packageName)) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
+ admin,
+ userId);
}
}
}
@@ -12178,24 +12230,40 @@
|| isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
- final int userId = caller.getUserId();
- synchronized (getLockObject()) {
- long id = mInjector.binderClearCallingIdentity();
- try {
- mIPackageManager.setBlockUninstallForUser(packageName, uninstallBlocked, userId);
- } catch (RemoteException re) {
- // Shouldn't happen.
- Slogf.e(LOG_TAG, "Failed to setBlockUninstallForUser", re);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
+ if (isCoexistenceEnabled(caller)) {
+ // TODO(b/260573124): Add correct enforcing admin when permission changes are
+ // merged, and don't forget to handle delegates! Enterprise admins assume
+ // component name isn't null.
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ who != null ? who : new ComponentName(callerPackage, "delegate"),
+ caller.getUserId());
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PACKAGE_UNINSTALL_BLOCKED(packageName),
+ admin,
+ uninstallBlocked,
+ caller.getUserId());
+ } else {
+ final int userId = caller.getUserId();
+ synchronized (getLockObject()) {
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ mIPackageManager.setBlockUninstallForUser(
+ packageName, uninstallBlocked, userId);
+ } catch (RemoteException re) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, "Failed to setBlockUninstallForUser", re);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+ if (uninstallBlocked) {
+ final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
+ pmi.removeNonSystemPackageSuspensions(packageName, userId);
+ pmi.removeDistractingPackageRestrictions(packageName, userId);
+ pmi.flushPackageRestrictions(userId);
}
}
- if (uninstallBlocked) {
- final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
- pmi.removeNonSystemPackageSuspensions(packageName, userId);
- pmi.removeDistractingPackageRestrictions(packageName, userId);
- pmi.flushPackageRestrictions(userId);
- }
+
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_UNINSTALL_BLOCKED)
.setAdmin(caller.getPackageName())
@@ -12204,6 +12272,26 @@
.write();
}
+ static void setUninstallBlockedUnchecked(
+ String packageName, boolean uninstallBlocked, int userId) {
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ AppGlobals.getPackageManager().setBlockUninstallForUser(
+ packageName, uninstallBlocked, userId);
+ } catch (RemoteException re) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, "Failed to setBlockUninstallForUser", re);
+ }
+ });
+ if (uninstallBlocked) {
+ final PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ pmi.removeNonSystemPackageSuspensions(packageName, userId);
+ pmi.removeDistractingPackageRestrictions(packageName, userId);
+ pmi.flushPackageRestrictions(userId);
+ }
+ }
+
@Override
public boolean isUninstallBlocked(ComponentName who, String packageName) {
// This function should return true if and only if the package is blocked by
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java
new file mode 100644
index 0000000..1665830
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain package in the policy engine's data
+ * structure.
+ */
+final class PackageSpecificPolicyKey extends PolicyKey {
+ private static final String ATTR_POLICY_KEY = "policy-key";
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_PERMISSION_NAME = "permission-name";
+
+ private final String mPackageName;
+
+ PackageSpecificPolicyKey(String key, String packageName) {
+ super(key);
+ mPackageName = Objects.requireNonNull((packageName));
+ }
+
+ PackageSpecificPolicyKey(String key) {
+ super(key);
+ mPackageName = null;
+ }
+
+ @Nullable
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
+ serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+ }
+
+ @Override
+ PackageSpecificPolicyKey readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
+ String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+ String permissionName = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_PERMISSION_NAME);
+ return new PackageSpecificPolicyKey(policyKey, packageName);
+ }
+
+ @Override
+ void writeToBundle(Bundle bundle) {
+ super.writeToBundle(bundle);
+ Bundle extraPolicyParams = new Bundle();
+ extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
+ bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PackageSpecificPolicyKey other = (PackageSpecificPolicyKey) o;
+ return Objects.equals(mKey, other.mKey)
+ && Objects.equals(mPackageName, other.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, mPackageName);
+ }
+
+ @Override
+ public String toString() {
+ return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java
new file mode 100644
index 0000000..b7d805e
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a PermissionGrantState policy in the policy engine's data structure.
+ */
+final class PermissionGrantStatePolicyKey extends PolicyKey {
+ private static final String ATTR_POLICY_KEY = "policy-key";
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_PERMISSION_NAME = "permission-name";
+
+ private final String mPackageName;
+ private final String mPermissionName;
+
+ PermissionGrantStatePolicyKey(String key, String packageName, String permissionName) {
+ super(key);
+ mPackageName = Objects.requireNonNull((packageName));
+ mPermissionName = Objects.requireNonNull((permissionName));
+ }
+
+ PermissionGrantStatePolicyKey(String key) {
+ super(key);
+ mPackageName = null;
+ mPermissionName = null;
+ }
+
+ @Nullable
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ @Nullable
+ String getPermissionName() {
+ return mPermissionName;
+ }
+
+ @Override
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
+ serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attribute(/* namespace= */ null, ATTR_PERMISSION_NAME, mPermissionName);
+ }
+
+ @Override
+ PermissionGrantStatePolicyKey readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
+ String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+ String permissionName = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_PERMISSION_NAME);
+ return new PermissionGrantStatePolicyKey(policyKey, packageName, permissionName);
+ }
+
+ @Override
+ void writeToBundle(Bundle bundle) {
+ super.writeToBundle(bundle);
+ Bundle extraPolicyParams = new Bundle();
+ extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
+ extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName);
+ bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PermissionGrantStatePolicyKey other = (PermissionGrantStatePolicyKey) o;
+ return Objects.equals(mKey, other.mKey)
+ && Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mPermissionName, other.mPermissionName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, mPackageName, mPermissionName);
+ }
+
+ @Override
+ public String toString() {
+ return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName + "; mPermissionName= "
+ + mPermissionName;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java
new file mode 100644
index 0000000..f8c07595
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.Nullable;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IntentResolver;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a PersistentPreferredActivity policy in the policy engine's data
+ * structure.
+ */
+final class PersistentPreferredActivityPolicyKey extends PolicyKey {
+ private static final String ATTR_POLICY_KEY = "policy-key";
+ private IntentFilter mFilter;
+
+ PersistentPreferredActivityPolicyKey(String policyKey, IntentFilter filter) {
+ super(policyKey);
+ mFilter = Objects.requireNonNull((filter));
+ }
+
+ PersistentPreferredActivityPolicyKey(String policyKey) {
+ super(policyKey);
+ mFilter = null;
+ }
+
+ @Nullable
+ IntentFilter getFilter() {
+ return mFilter;
+ }
+
+ @Override
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
+ mFilter.writeToXml(serializer);
+ }
+
+ @Override
+ PersistentPreferredActivityPolicyKey readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
+ IntentFilter filter = new IntentFilter();
+ filter.readFromXml(parser);
+ return new PersistentPreferredActivityPolicyKey(policyKey, filter);
+ }
+
+ @Override
+ void writeToBundle(Bundle bundle) {
+ super.writeToBundle(bundle);
+ Bundle extraPolicyParams = new Bundle();
+ extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter);
+ bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersistentPreferredActivityPolicyKey other = (PersistentPreferredActivityPolicyKey) o;
+ return Objects.equals(mKey, other.mKey)
+ && IntentResolver.filterEquals(mFilter, other.mFilter);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, mFilter);
+ }
+
+ @Override
+ public String toString() {
+ return "mKey= " + mKey + "; mFilter= " + mFilter;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index cfb3db0..ab1658f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -19,9 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.PolicyUpdatesReceiver;
+import android.content.ComponentName;
import android.content.Context;
-import android.os.Bundle;
+import android.content.IntentFilter;
import com.android.internal.util.function.QuadFunction;
import com.android.modules.utils.TypedXmlPullParser;
@@ -47,27 +47,25 @@
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(false, true));
- private static final String ATTR_POLICY_KEY = "policy-key";
- private static final String ATTR_POLICY_DEFINITION_KEY = "policy-type-key";
- private static final String ATTR_CALLBACK_ARGS_SIZE = "size";
- private static final String ATTR_CALLBACK_ARGS_KEY = "key";
- private static final String ATTR_CALLBACK_ARGS_VALUE = "value";
-
+ private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>(
+ List.of(true, false));
static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
- DevicePolicyManager.AUTO_TIMEZONE_POLICY,
+ new DefaultPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY),
// auto timezone is enabled by default, hence disabling it is more restrictive.
FALSE_MORE_RESTRICTIVE,
POLICY_FLAG_GLOBAL_ONLY_POLICY,
- (Boolean value, Context context, Integer userId, Bundle args) ->
+ (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
new BooleanPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
// actual permission grant policy with the correct arguments (packageName and permission name)
// when reading the policies from xml.
- private static final PolicyDefinition<Integer> PERMISSION_GRANT_NO_ARGS =
- new PolicyDefinition<>(DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY,
+ static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
+ new PolicyDefinition<>(
+ new PermissionGrantStatePolicyKey(
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY),
// TODO: is this really the best mechanism, what makes denied more
// restrictive than
// granted?
@@ -79,71 +77,125 @@
PolicyEnforcerCallbacks::setPermissionGrantState,
new IntegerPolicySerializer());
+ /**
+ * Passing in {@code null} for {@code packageName} or {@code permissionName} will return a
+ * {@link #GENERIC_PERMISSION_GRANT}.
+ */
static PolicyDefinition<Integer> PERMISSION_GRANT(
- @NonNull String packageName, @NonNull String permission) {
- Bundle callbackArgs = new Bundle();
- callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME, packageName);
- callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME, permission);
- return PERMISSION_GRANT_NO_ARGS.setArgs(
- DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission), callbackArgs);
+ @NonNull String packageName, @NonNull String permissionName) {
+ if (packageName == null || permissionName == null) {
+ return GENERIC_PERMISSION_GRANT;
+ }
+ return GENERIC_PERMISSION_GRANT.createPolicyDefinition(
+ new PermissionGrantStatePolicyKey(
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY,
+ packageName,
+ permissionName));
}
static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
- DevicePolicyManager.LOCK_TASK_POLICY,
+ new DefaultPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY),
new TopPriority<>(List.of(
// TODO(b/258166155): add correct device lock role name
EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_LOCAL_ONLY_POLICY,
- (LockTaskPolicy value, Context context, Integer userId, Bundle args) ->
+ (LockTaskPolicy value, Context context, Integer userId, PolicyKey policyKey) ->
PolicyEnforcerCallbacks.setLockTask(value, context, userId),
new LockTaskPolicy.LockTaskPolicySerializer());
static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = new PolicyDefinition<>(
- DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES,
+ new DefaultPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY),
new SetUnion<>(),
- (Set<String> value, Context context, Integer userId, Bundle args) ->
+ (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
new SetPolicySerializer<>());
+ // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+ // actual permission grant policy with the correct arguments (packageName and permission name)
+ // when reading the policies from xml.
+ static PolicyDefinition<ComponentName> GENERIC_PERSISTENT_PREFERRED_ACTIVITY =
+ new PolicyDefinition<>(
+ new PersistentPreferredActivityPolicyKey(
+ DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY),
+ new TopPriority<>(List.of(
+ // TODO(b/258166155): add correct device lock role name
+ EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
+ EnforcingAdmin.DPC_AUTHORITY)),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::addPersistentPreferredActivity,
+ new ComponentNamePolicySerializer());
+
+ /**
+ * Passing in {@code null} for {@code intentFilter} will return
+ * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}.
+ */
+ static PolicyDefinition<ComponentName> PERSISTENT_PREFERRED_ACTIVITY(
+ IntentFilter intentFilter) {
+ if (intentFilter == null) {
+ return GENERIC_PERSISTENT_PREFERRED_ACTIVITY;
+ }
+ return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition(
+ new PersistentPreferredActivityPolicyKey(
+ DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY, intentFilter));
+ }
+
+ // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+ // actual uninstall blocked policy with the correct arguments (i.e. packageName)
+ // when reading the policies from xml.
+ static PolicyDefinition<Boolean> GENERIC_PACKAGE_UNINSTALL_BLOCKED =
+ new PolicyDefinition<>(
+ new PackageSpecificPolicyKey(
+ DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY),
+ TRUE_MORE_RESTRICTIVE,
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::setUninstallBlocked,
+ new BooleanPolicySerializer());
+
+ /**
+ * Passing in {@code null} for {@code packageName} will return
+ * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}.
+ */
+ static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(
+ String packageName) {
+ if (packageName == null) {
+ return GENERIC_PACKAGE_UNINSTALL_BLOCKED;
+ }
+ return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition(
+ new PackageSpecificPolicyKey(
+ DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
+ }
+
private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
- DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS,
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, GENERIC_PERMISSION_GRANT,
DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK,
- DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES, USER_CONTROLLED_DISABLED_PACKAGES
+ DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY,
+ USER_CONTROLLED_DISABLED_PACKAGES,
+ DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
+ GENERIC_PERSISTENT_PREFERRED_ACTIVITY,
+ DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED
);
- private final String mPolicyKey;
- private final String mPolicyDefinitionKey;
+ private final PolicyKey mPolicyKey;
private final ResolutionMechanism<V> mResolutionMechanism;
private final int mPolicyFlags;
// A function that accepts policy to apple, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
- private final QuadFunction<V, Context, Integer, Bundle, Boolean> mPolicyEnforcerCallback;
- private final Bundle mCallbackArgs;
+ private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
private final PolicySerializer<V> mPolicySerializer;
- private PolicyDefinition<V> setArgs(String key, Bundle callbackArgs) {
- return new PolicyDefinition<>(key, mPolicyDefinitionKey, mResolutionMechanism,
- mPolicyFlags, mPolicyEnforcerCallback, mPolicySerializer, callbackArgs);
+ private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
+ return new PolicyDefinition<>(key, mResolutionMechanism, mPolicyFlags,
+ mPolicyEnforcerCallback, mPolicySerializer);
}
@NonNull
- String getPolicyKey() {
+ PolicyKey getPolicyKey() {
return mPolicyKey;
}
- @NonNull
- String getPolicyDefinitionKey() {
- return mPolicyDefinitionKey;
- }
-
- @Nullable
- Bundle getCallbackArgs() {
- return mCallbackArgs;
- }
-
/**
* Returns {@code true} if the policy is a global policy by nature and can't be applied locally.
*/
@@ -164,7 +216,7 @@
}
boolean enforcePolicy(@Nullable V value, Context context, int userId) {
- return mPolicyEnforcerCallback.apply(value, context, userId, mCallbackArgs);
+ return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
}
/**
@@ -172,93 +224,54 @@
* {@link Object#equals} implementation.
*/
private PolicyDefinition(
- String key,
+ PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
/**
- * Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals} implementation.
+ * Callers must ensure that custom {@code policyKeys} and {@code V} have an appropriate
+ * {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- String key,
+ PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
- this(key, key, resolutionMechanism, policyFlags, policyEnforcerCallback,
- policySerializer, /* callbackArs= */ null);
- }
-
- /**
- * Callers must ensure that {@code policyType} have implemented an appropriate
- * {@link Object#equals} implementation.
- */
- private PolicyDefinition(
- String policyKey,
- String policyDefinitionKey,
- ResolutionMechanism<V> resolutionMechanism,
- int policyFlags,
- QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
- PolicySerializer<V> policySerializer,
- Bundle callbackArgs) {
mPolicyKey = policyKey;
- mPolicyDefinitionKey = policyDefinitionKey;
mResolutionMechanism = resolutionMechanism;
mPolicyFlags = policyFlags;
mPolicyEnforcerCallback = policyEnforcerCallback;
mPolicySerializer = policySerializer;
- mCallbackArgs = callbackArgs;
// TODO: maybe use this instead of manually adding to the map
// sPolicyDefinitions.put(policyDefinitionKey, this);
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mPolicyKey);
- serializer.attribute(
- /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY, mPolicyDefinitionKey);
- serializer.attributeInt(
- /* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE,
- mCallbackArgs == null ? 0 : mCallbackArgs.size());
- if (mCallbackArgs != null) {
- int i = 0;
- for (String key : mCallbackArgs.keySet()) {
- serializer.attribute(/* namespace= */ null,
- ATTR_CALLBACK_ARGS_KEY + i, key);
- serializer.attribute(/* namespace= */ null,
- ATTR_CALLBACK_ARGS_VALUE + i, mCallbackArgs.getString(key));
- i++;
- }
- }
+ // TODO: here and elsewhere, add tags to ensure attributes aren't overridden by duplication.
+ mPolicyKey.saveToXml(serializer);
}
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
- String policyDefinitionKey = parser.getAttributeValue(
- /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY);
- int size = parser.getAttributeInt(/* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE);
- Bundle callbackArgs = new Bundle();
-
- for (int i = 0; i < size; i++) {
- String key = parser.getAttributeValue(
- /* namespace= */ null, ATTR_CALLBACK_ARGS_KEY + i);
- String value = parser.getAttributeValue(
- /* namespace= */ null, ATTR_CALLBACK_ARGS_VALUE + i);
- callbackArgs.putString(key, value);
- }
-
// TODO: can we avoid casting?
- if (callbackArgs.isEmpty()) {
- return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey);
- } else {
- return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey).setArgs(
- policyKey, callbackArgs);
- }
+ PolicyKey policyKey = readPolicyKeyFromXml(parser);
+ PolicyDefinition<V> genericPolicyDefinition =
+ (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey);
+ return genericPolicyDefinition.createPolicyDefinition(policyKey);
+ }
+
+ static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // TODO: can we avoid casting?
+ PolicyKey policyKey = DefaultPolicyKey.readGenericPolicyKeyFromXml(parser);
+ PolicyDefinition<V> genericPolicyDefinition =
+ (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey);
+ return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
}
void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
@@ -273,8 +286,7 @@
@Override
public String toString() {
- return "PolicyDefinition { mPolicyKey= " + mPolicyKey + ", mPolicyDefinitionKey= "
- + mPolicyDefinitionKey + ", mResolutionMechanism= " + mResolutionMechanism
- + ", mCallbackArgs= " + mCallbackArgs + ", mPolicyFlags= " + mPolicyFlags + " }";
+ return "PolicyDefinition{ mPolicyKey= " + mPolicyKey + ", mResolutionMechanism= "
+ + mResolutionMechanism + ", mPolicyFlags= " + mPolicyFlags + " }";
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 5664d2b..e2aa23d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -18,17 +18,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.PolicyUpdatesReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
-import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
import android.provider.Settings;
+import android.util.Slog;
import com.android.server.LocalServices;
import com.android.server.utils.Slogf;
@@ -58,19 +62,15 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
- @NonNull Bundle args) {
+ @NonNull PolicyKey policyKey) {
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
- if (args == null
- || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME)
- || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME)) {
- throw new IllegalArgumentException("Package name and permission name must be "
- + "provided as arguments.");
+ if (!(policyKey instanceof PermissionGrantStatePolicyKey)) {
+ throw new IllegalArgumentException("policyKey is not of type "
+ + "PermissionGrantStatePolicyKey");
}
-
- String packageName = args.getString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME);
- String permissionName = args.getString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME);
- Objects.requireNonNull(packageName);
- Objects.requireNonNull(permissionName);
+ PermissionGrantStatePolicyKey parsedKey = (PermissionGrantStatePolicyKey) policyKey;
+ Objects.requireNonNull(parsedKey.getPermissionName());
+ Objects.requireNonNull(parsedKey.getPackageName());
Objects.requireNonNull(context);
int value = grantState == null
@@ -81,7 +81,7 @@
// TODO: remove canAdminGrantSensorPermissions once we expose a new method in
// permissionController that doesn't need it.
AdminPermissionControlParams permissionParams = new AdminPermissionControlParams(
- packageName, permissionName, value,
+ parsedKey.getPackageName(), parsedKey.getPermissionName(), value,
/* canAdminGrantSensorPermissions= */ true);
getPermissionControllerManager(context, UserHandle.of(userId))
// TODO: remove callingPackage param and stop passing context.getPackageName()
@@ -150,4 +150,51 @@
packages == null ? null : packages.stream().toList()));
return true;
}
+
+ static boolean addPersistentPreferredActivity(
+ @Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ if (!(policyKey instanceof PersistentPreferredActivityPolicyKey)) {
+ throw new IllegalArgumentException("policyKey is not of type "
+ + "PersistentPreferredActivityPolicyKey");
+ }
+ PersistentPreferredActivityPolicyKey parsedKey =
+ (PersistentPreferredActivityPolicyKey) policyKey;
+ IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter());
+
+ IPackageManager packageManager = AppGlobals.getPackageManager();
+ if (preferredActivity != null) {
+ packageManager.addPersistentPreferredActivity(
+ filter, preferredActivity, userId);
+ } else {
+ packageManager.clearPersistentPreferredActivity(filter, userId);
+ }
+ packageManager.flushPackageRestrictionsAsUser(userId);
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
+ }
+ });
+ return true;
+ }
+
+ static boolean setUninstallBlocked(
+ @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ if (!(policyKey instanceof PackageSpecificPolicyKey)) {
+ throw new IllegalArgumentException("policyKey is not of type "
+ + "PackageSpecificPolicyKey");
+ }
+ PackageSpecificPolicyKey parsedKey = (PackageSpecificPolicyKey) policyKey;
+ String packageName = Objects.requireNonNull(parsedKey.getPackageName());
+ DevicePolicyManagerService.setUninstallBlockedUnchecked(
+ packageName,
+ uninstallBlocked != null && uninstallBlocked,
+ userId);
+ return true;
+ }));
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java
new file mode 100644
index 0000000..571f0ee
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Abstract class used to identify a policy in the policy engine's data structure.
+ */
+abstract class PolicyKey {
+ private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key";
+
+ protected final String mKey;
+
+ PolicyKey(String policyKey) {
+ mKey = Objects.requireNonNull(policyKey);
+ }
+
+ String getKey() {
+ return mKey;
+ }
+
+ boolean hasSameKeyAs(PolicyKey other) {
+ if (other == null) {
+ return false;
+ }
+ return mKey.equals(other.mKey);
+ }
+
+ void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(/* namespace= */ null, ATTR_GENERIC_POLICY_KEY, mKey);
+ }
+
+ PolicyKey readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // No need to read anything
+ return this;
+ }
+
+ void writeToBundle(Bundle bundle) {
+ bundle.putString(EXTRA_POLICY_KEY, mKey);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PolicyKey other = (PolicyKey) o;
+ return Objects.equals(mKey, other.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey);
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
index dbc0da7..c8797e2 100644
--- a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.annotation.UserIdInt;
import android.app.job.JobScheduler;
import android.content.Context;
@@ -31,6 +33,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
@@ -45,14 +49,20 @@
private BackupManagerConstants mConstants;
private ShadowJobScheduler mShadowJobScheduler;
+ @Mock
+ private UserBackupManagerService mUserBackupManagerService;
+
@UserIdInt private int mUserOneId;
@UserIdInt private int mUserTwoId;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
mConstants.start();
+ when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
@@ -69,8 +79,8 @@
@Test
public void testSchedule_afterScheduling_jobExists() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
@@ -78,18 +88,34 @@
@Test
public void testCancel_afterCancelling_jobDoesntExist() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
FullBackupJob.cancel(mUserOneId, mContext);
FullBackupJob.cancel(mUserTwoId, mContext);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
}
+
+ @Test
+ public void testSchedule_isNoopIfDisabled() {
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+ }
+
+ @Test
+ public void testSchedule_schedulesJobIfEnabled() {
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+ }
//
@Test
public void testSchedule_onlySchedulesForRequestedUser() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
@@ -97,8 +123,8 @@
//
@Test
public void testCancel_onlyCancelsForRequestedUser() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
FullBackupJob.cancel(mUserOneId, mContext);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 1c5fac2..712ac55 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Handler;
@@ -30,6 +32,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -41,14 +45,20 @@
private Context mContext;
private BackupManagerConstants mConstants;
+ @Mock
+ private UserBackupManagerService mUserBackupManagerService;
+
@UserIdInt private int mUserOneId;
@UserIdInt private int mUserTwoId;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
mConstants.start();
+ when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
mUserOneId = UserHandle.USER_SYSTEM;
mUserTwoId = mUserOneId + 1;
@@ -62,6 +72,22 @@
}
@Test
+ public void testSchedule_isNoopIfDisabled() {
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+ }
+
+ @Test
+ public void testSchedule_schedulesJobIfEnabled() {
+ when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+ }
+
+ @Test
public void testIsScheduled_beforeScheduling_returnsFalse() {
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -69,8 +95,8 @@
@Test
public void testIsScheduled_afterScheduling_returnsTrue() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
@@ -78,8 +104,8 @@
@Test
public void testIsScheduled_afterCancelling_returnsFalse() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
KeyValueBackupJob.cancel(mUserOneId, mContext);
KeyValueBackupJob.cancel(mUserTwoId, mContext);
@@ -89,7 +115,7 @@
@Test
public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -97,8 +123,8 @@
@Test
public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
KeyValueBackupJob.cancel(mUserOneId, mContext);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 2878743..02e0bbf 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1381,8 +1381,8 @@
* BackupManagerConstants)} that throws an {@link IllegalArgumentException}.
*/
public static void schedule(int userId, Context ctx, long delay,
- BackupManagerConstants constants) {
- ShadowKeyValueBackupJob.schedule(userId, ctx, delay, constants);
+ UserBackupManagerService userBackupManagerService) {
+ ShadowKeyValueBackupJob.schedule(userId, ctx, delay, userBackupManagerService);
throw new IllegalArgumentException();
}
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
index f90ea6a..d66f6ef 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
@@ -21,6 +21,7 @@
import com.android.server.backup.BackupManagerConstants;
import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.UserBackupManagerService;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -35,7 +36,7 @@
@Implementation
protected static void schedule(int userId, Context ctx, long delay,
- BackupManagerConstants constants) {
+ UserBackupManagerService userBackupManagerService) {
callingUid = Binder.getCallingUid();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index f13de12..d03d196 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -34,6 +34,7 @@
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import androidx.test.annotation.UiThreadTest;
@@ -154,6 +155,15 @@
}
@Test
+ public void testGetCurrentAndTargetUserIds() {
+ mockCurrentAndTargetUser(USER_ID, OTHER_USER_ID);
+
+ assertWithMessage("getCurrentAndTargetUserIds()")
+ .that(mUms.getCurrentAndTargetUserIds())
+ .isEqualTo(new Pair<>(USER_ID, OTHER_USER_ID));
+ }
+
+ @Test
public void testGetCurrentUserId() {
mockCurrentUser(USER_ID);
@@ -329,6 +339,14 @@
when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
}
+ private void mockCurrentAndTargetUser(@UserIdInt int currentUserId,
+ @UserIdInt int targetUserId) {
+ mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
+
+ when(mActivityManagerInternal.getCurrentAndTargetUserIds())
+ .thenReturn(new Pair<>(currentUserId, targetUserId));
+ }
+
private <T> void mockGetLocalService(Class<T> serviceClass, T service) {
doReturn(service).when(() -> LocalServices.getService(serviceClass));
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index bbe8907..c9f00d7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -23,12 +23,12 @@
import android.app.ActivityManager;
import android.app.IStopUserCallback;
-import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
+import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -37,10 +37,12 @@
import com.android.internal.util.FunctionalUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -56,18 +58,30 @@
private static final String TAG = "UserLifecycleStressTest";
// TODO: Make this smaller once we have improved it.
private static final int TIMEOUT_IN_SECOND = 40;
- private static final int NUM_ITERATIONS = 10;
+ private static final int NUM_ITERATIONS = 8;
private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;
private Context mContext;
private UserManager mUserManager;
private ActivityManager mActivityManager;
+ private UserSwitchWaiter mUserSwitchWaiter;
+ private String mRemoveGuestOnExitOriginalValue;
@Before
- public void setup() {
+ public void setup() throws RemoteException {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mUserManager = mContext.getSystemService(UserManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
+ mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ mUserSwitchWaiter.close();
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
}
/**
@@ -101,10 +115,13 @@
* 1. While the guest user is in foreground, mark it for deletion.
* 2. Create a new guest. (This wouldn't be possible if the old one wasn't marked for deletion)
* 3. Switch to newly created guest.
- * 4. Remove the previous guest before waiting for switch to complete.
+ * 4. Remove the previous guest after the switch is complete.
**/
@Test
- public void switchToExistingGuestAndStartOverStressTest() throws Exception {
+ public void switchToExistingGuestAndStartOverStressTest() {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, "0");
+
if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
switchUser(USER_SYSTEM);
}
@@ -135,14 +152,15 @@
.isNotNull();
Log.d(TAG, "Switching to the new guest");
- switchUserThenRun(newGuest.id, () -> {
- if (currentGuestId != USER_NULL) {
- Log.d(TAG, "Removing the previous guest before waiting for switch to complete");
- assertWithMessage("Couldn't remove guest")
- .that(mUserManager.removeUser(currentGuestId))
- .isTrue();
- }
- });
+ switchUser(newGuest.id);
+
+ if (currentGuestId != USER_NULL) {
+ Log.d(TAG, "Removing the previous guest");
+ assertWithMessage("Couldn't remove guest")
+ .that(mUserManager.removeUser(currentGuestId))
+ .isTrue();
+ }
+
Log.d(TAG, "Switching back to the system user");
switchUser(USER_SYSTEM);
@@ -174,33 +192,14 @@
}
/** Starts the given user in the foreground and waits for the switch to finish. */
- private void switchUser(int userId) throws RemoteException, InterruptedException {
- switchUserThenRun(userId, null);
- }
+ private void switchUser(int userId) {
+ Log.d(TAG, "Switching to user " + userId);
- /**
- * Starts the given user in the foreground. And runs the given Runnable right after
- * am.switchUser call, before waiting for the actual user switch to be complete.
- **/
- private void switchUserThenRun(int userId, Runnable runAfterSwitchBeforeWait)
- throws RemoteException, InterruptedException {
- runWithLatch("switch user", countDownLatch -> {
- ActivityManager.getService().registerUserSwitchObserver(
- new UserSwitchObserver() {
- @Override
- public void onUserSwitchComplete(int newUserId) {
- if (userId == newUserId) {
- countDownLatch.countDown();
- }
- }
- }, TAG);
- Log.d(TAG, "Switching to user " + userId);
- assertWithMessage("Failed to switch to user")
- .that(mActivityManager.switchUser(userId))
- .isTrue();
- if (runAfterSwitchBeforeWait != null) {
- runAfterSwitchBeforeWait.run();
- }
+ mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
+ assertWithMessage("Could not start switching to user " + userId)
+ .that(mActivityManager.switchUser(userId)).isTrue();
+ }, /* onFail= */ () -> {
+ throw new AssertionError("Could not complete switching to user " + userId);
});
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 1889d9a..76a13f1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -75,7 +75,7 @@
private static final int REMOVE_CHECK_INTERVAL_MILLIS = 500; // 0.5 seconds
private static final int REMOVE_TIMEOUT_MILLIS = 60 * 1000; // 60 seconds
- private static final int SWITCH_USER_TIMEOUT_MILLIS = 40 * 1000; // 40 seconds
+ private static final int SWITCH_USER_TIMEOUT_SECONDS = 40; // 40 seconds
// Packages which are used during tests.
private static final String[] PACKAGES = new String[] {
@@ -87,19 +87,21 @@
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private final Object mUserRemoveLock = new Object();
- private final Object mUserSwitchLock = new Object();
private UserManager mUserManager = null;
+ private ActivityManager mActivityManager;
private PackageManager mPackageManager;
private List<Integer> usersToRemove;
+ private UserSwitchWaiter mUserSwitchWaiter;
@Before
public void setUp() throws Exception {
mUserManager = UserManager.get(mContext);
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
mPackageManager = mContext.getPackageManager();
+ mUserSwitchWaiter = new UserSwitchWaiter(TAG, SWITCH_USER_TIMEOUT_SECONDS);
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -109,11 +111,6 @@
mUserRemoveLock.notifyAll();
}
break;
- case Intent.ACTION_USER_SWITCHED:
- synchronized (mUserSwitchLock) {
- mUserSwitchLock.notifyAll();
- }
- break;
}
}
}, filter);
@@ -124,6 +121,7 @@
@After
public void tearDown() throws Exception {
+ mUserSwitchWaiter.close();
for (Integer userId : usersToRemove) {
removeUser(userId);
}
@@ -322,6 +320,66 @@
@MediumTest
@Test
+ public void testRemoveUserShouldNotRemoveCurrentUser() {
+ final int startUser = ActivityManager.getCurrentUser();
+ final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+ // Switch to the user just created.
+ switchUser(testUser.id);
+
+ assertWithMessage("Current user should not be removed")
+ .that(mUserManager.removeUser(testUser.id))
+ .isFalse();
+
+ // Switch back to the starting user.
+ switchUser(startUser);
+
+ // Now we can remove the user
+ removeUser(testUser.id);
+ }
+
+ @MediumTest
+ @Test
+ public void testRemoveUserShouldNotRemoveCurrentUser_DuringUserSwitch() {
+ final int startUser = ActivityManager.getCurrentUser();
+ final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+ // Switch to the user just created.
+ switchUser(testUser.id);
+
+ switchUserThenRun(startUser, () -> {
+ // While the user switch is happening, call removeUser for the current user.
+ assertWithMessage("Current user should not be removed during user switch")
+ .that(mUserManager.removeUser(testUser.id))
+ .isFalse();
+ });
+ assertThat(hasUser(testUser.id)).isTrue();
+
+ // Now we can remove the user
+ removeUser(testUser.id);
+ }
+
+ @MediumTest
+ @Test
+ public void testRemoveUserShouldNotRemoveTargetUser_DuringUserSwitch() {
+ final int startUser = ActivityManager.getCurrentUser();
+ final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+
+ switchUserThenRun(testUser.id, () -> {
+ // While the user switch is happening, call removeUser for the target user.
+ assertWithMessage("Target user should not be removed during user switch")
+ .that(mUserManager.removeUser(testUser.id))
+ .isFalse();
+ });
+ assertThat(hasUser(testUser.id)).isTrue();
+
+ // Switch back to the starting user.
+ switchUser(startUser);
+
+ // Now we can remove the user
+ removeUser(testUser.id);
+ }
+
+ @MediumTest
+ @Test
public void testRemoveUserWhenPossible_restrictedReturnsError() throws Exception {
final int currentUser = ActivityManager.getCurrentUser();
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
@@ -383,7 +441,7 @@
final UserInfo otherUser = createUser("User 1", /* flags= */ UserInfo.FLAG_ADMIN);
UserHandle mainUser = mUserManager.getMainUser();
- switchUser(otherUser.id, null, true);
+ switchUser(otherUser.id);
assertThat(mUserManager.removeUserWhenPossible(mainUser,
/* overrideDevicePolicy= */ false))
@@ -393,7 +451,7 @@
assertThat(hasUser(mainUser.getIdentifier())).isTrue();
// Switch back to the starting user.
- switchUser(currentUser, null, true);
+ switchUser(currentUser);
}
@MediumTest
@@ -411,7 +469,7 @@
final int startUser = ActivityManager.getCurrentUser();
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
// Switch to the user just created.
- switchUser(user1.id, null, /* ignoreHandle= */ true);
+ switchUser(user1.id);
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
/* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
@@ -420,7 +478,7 @@
assertThat(getUser(user1.id).isEphemeral()).isTrue();
// Switch back to the starting user.
- switchUser(startUser, null, /* ignoreHandle= */ true);
+ switchUser(startUser);
// User is removed once switch is complete
synchronized (mUserRemoveLock) {
@@ -431,6 +489,55 @@
@MediumTest
@Test
+ public void testRemoveUserWhenPossible_currentUserSetEphemeral_duringUserSwitch() {
+ final int startUser = ActivityManager.getCurrentUser();
+ final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+ // Switch to the user just created.
+ switchUser(testUser.id);
+
+ switchUserThenRun(startUser, () -> {
+ // While the user switch is happening, call removeUserWhenPossible for the current user.
+ assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
+ .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+
+ assertThat(hasUser(testUser.id)).isTrue();
+ assertThat(getUser(testUser.id).isEphemeral()).isTrue();
+ });
+
+ // User is removed once switch is complete
+ synchronized (mUserRemoveLock) {
+ waitForUserRemovalLocked(testUser.id);
+ }
+ assertThat(hasUser(testUser.id)).isFalse();
+ }
+
+ @MediumTest
+ @Test
+ public void testRemoveUserWhenPossible_targetUserSetEphemeral_duringUserSwitch() {
+ final int startUser = ActivityManager.getCurrentUser();
+ final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+
+ switchUserThenRun(testUser.id, () -> {
+ // While the user switch is happening, call removeUserWhenPossible for the target user.
+ assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
+ .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+
+ assertThat(hasUser(testUser.id)).isTrue();
+ assertThat(getUser(testUser.id).isEphemeral()).isTrue();
+ });
+
+ // Switch back to the starting user.
+ switchUser(startUser);
+
+ // User is removed once switch is complete
+ synchronized (mUserRemoveLock) {
+ waitForUserRemovalLocked(testUser.id);
+ }
+ assertThat(hasUser(testUser.id)).isFalse();
+ }
+
+ @MediumTest
+ @Test
public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
synchronized (mUserRemoveLock) {
@@ -1149,33 +1256,30 @@
@LargeTest
@Test
public void testSwitchUser() {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
- final int startUser = am.getCurrentUser();
+ final int startUser = ActivityManager.getCurrentUser();
UserInfo user = createUser("User", 0);
assertThat(user).isNotNull();
// Switch to the user just created.
- switchUser(user.id, null, true);
+ switchUser(user.id);
// Switch back to the starting user.
- switchUser(startUser, null, true);
+ switchUser(startUser);
}
@LargeTest
@Test
public void testSwitchUserByHandle() {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
- final int startUser = am.getCurrentUser();
+ final int startUser = ActivityManager.getCurrentUser();
UserInfo user = createUser("User", 0);
assertThat(user).isNotNull();
// Switch to the user just created.
- switchUser(-1, user.getUserHandle(), false);
+ switchUser(user.getUserHandle());
// Switch back to the starting user.
- switchUser(-1, UserHandle.of(startUser), false);
+ switchUser(UserHandle.of(startUser));
}
@Test
public void testSwitchUserByHandle_ThrowsException() {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
- assertThrows(IllegalArgumentException.class, () -> am.switchUser(null));
+ assertThrows(IllegalArgumentException.class, () -> mActivityManager.switchUser(null));
}
@MediumTest
@@ -1319,31 +1423,43 @@
}
/**
- * @param userId value will be used to call switchUser(int) only if ignoreHandle is false.
- * @param user value will be used to call switchUser(UserHandle) only if ignoreHandle is true.
- * @param ignoreHandle if true, switchUser(int) will be called with the provided userId,
- * else, switchUser(UserHandle) will be called with the provided user.
- */
- private void switchUser(int userId, UserHandle user, boolean ignoreHandle) {
- synchronized (mUserSwitchLock) {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
- if (ignoreHandle) {
- am.switchUser(userId);
- } else {
- am.switchUser(user);
+ * Starts the given user in the foreground. And waits for the user switch to be complete.
+ **/
+ private void switchUser(UserHandle user) {
+ final int userId = user.getIdentifier();
+ Slog.d(TAG, "Switching to user " + userId);
+
+ mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
+ assertWithMessage("Could not start switching to user " + userId)
+ .that(mActivityManager.switchUser(user)).isTrue();
+ }, /* onFail= */ () -> {
+ throw new AssertionError("Could not complete switching to user " + userId);
+ });
+ }
+
+ /**
+ * Starts the given user in the foreground. And waits for the user switch to be complete.
+ **/
+ private void switchUser(int userId) {
+ switchUserThenRun(userId, null);
+ }
+
+ /**
+ * Starts the given user in the foreground. And runs the given Runnable right after
+ * am.switchUser call, before waiting for the actual user switch to be complete.
+ **/
+ private void switchUserThenRun(int userId, Runnable runAfterSwitchBeforeWait) {
+ Slog.d(TAG, "Switching to user " + userId);
+ mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
+ // Start switching to user
+ assertWithMessage("Could not start switching to user " + userId)
+ .that(mActivityManager.switchUser(userId)).isTrue();
+
+ // While the user switch is happening, call runAfterSwitchBeforeWait.
+ if (runAfterSwitchBeforeWait != null) {
+ runAfterSwitchBeforeWait.run();
}
- long time = System.currentTimeMillis();
- try {
- mUserSwitchLock.wait(SWITCH_USER_TIMEOUT_MILLIS);
- } catch (InterruptedException ie) {
- Thread.currentThread().interrupt();
- return;
- }
- if (System.currentTimeMillis() - time > SWITCH_USER_TIMEOUT_MILLIS) {
- fail("Timeout waiting for the user switch to u"
- + (ignoreHandle ? userId : user.getIdentifier()));
- }
- }
+ }, () -> fail("Could not complete switching to user " + userId));
}
private void removeUser(UserHandle user) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSwitchWaiter.java b/services/tests/servicestests/src/com/android/server/pm/UserSwitchWaiter.java
new file mode 100644
index 0000000..d948570
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSwitchWaiter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.IUserSwitchObserver;
+import android.app.UserSwitchObserver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.FunctionalUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class UserSwitchWaiter implements Closeable {
+
+ private final String mTag;
+ private final int mTimeoutInSecond;
+ private final IActivityManager mActivityManager;
+ private final IUserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ getSemaphoreSwitchComplete(newUserId).release();
+ }
+
+ @Override
+ public void onLockedBootComplete(int newUserId) {
+ getSemaphoreBootComplete(newUserId).release();
+ }
+ };
+
+ private final Map<Integer, Semaphore> mSemaphoresMapSwitchComplete = new ConcurrentHashMap<>();
+ private Semaphore getSemaphoreSwitchComplete(final int userId) {
+ return mSemaphoresMapSwitchComplete.computeIfAbsent(userId,
+ (Integer absentKey) -> new Semaphore(0));
+ }
+
+ private final Map<Integer, Semaphore> mSemaphoresMapBootComplete = new ConcurrentHashMap<>();
+ private Semaphore getSemaphoreBootComplete(final int userId) {
+ return mSemaphoresMapBootComplete.computeIfAbsent(userId,
+ (Integer absentKey) -> new Semaphore(0));
+ }
+
+ public UserSwitchWaiter(String tag, int timeoutInSecond) throws RemoteException {
+ mTag = tag;
+ mTimeoutInSecond = timeoutInSecond;
+ mActivityManager = ActivityManager.getService();
+
+ mActivityManager.registerUserSwitchObserver(mUserSwitchObserver, mTag);
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ mActivityManager.unregisterUserSwitchObserver(mUserSwitchObserver);
+ } catch (RemoteException e) {
+ Log.e(mTag, "Failed to unregister user switch observer", e);
+ }
+ }
+
+ public void runThenWaitUntilSwitchCompleted(int userId,
+ FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) {
+ final Semaphore semaphore = getSemaphoreSwitchComplete(userId);
+ semaphore.drainPermits();
+ runnable.run();
+ waitForSemaphore(semaphore, onFail);
+ }
+
+ public void runThenWaitUntilBootCompleted(int userId,
+ FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) {
+ final Semaphore semaphore = getSemaphoreBootComplete(userId);
+ semaphore.drainPermits();
+ runnable.run();
+ waitForSemaphore(semaphore, onFail);
+ }
+
+ private void waitForSemaphore(Semaphore semaphore, Runnable onFail) {
+ boolean success = false;
+ try {
+ success = semaphore.tryAcquire(mTimeoutInSecond, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(mTag, "Thread interrupted unexpectedly.", e);
+ }
+ if (!success && onFail != null) {
+ onFail.run();
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index bc23fa3..78707d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2066,7 +2066,8 @@
// Update the forced size and density in settings and the unique id to simualate a display
// remap.
dc.mWmService.mDisplayWindowSettings.setForcedSize(dc, forcedWidth, forcedHeight);
- dc.mWmService.mDisplayWindowSettings.setForcedDensity(dc, forcedDensity, 0 /* userId */);
+ dc.mWmService.mDisplayWindowSettings.setForcedDensity(displayInfo, forcedDensity,
+ 0 /* userId */);
dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test";
// Trigger display changed.
dc.onDisplayChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index c398a0a..fb4f2ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -236,8 +236,8 @@
@Test
public void testSetForcedDensity() {
- mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay, 600 /* density */,
- 0 /* userId */);
+ mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay.getDisplayInfo(),
+ 600 /* density */, 0 /* userId */);
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
assertEquals(600 /* density */, mSecondaryDisplay.mBaseDisplayDensity);
@@ -439,8 +439,9 @@
public void testDisplayWindowSettingsAppliedOnDisplayReady() {
// Set forced densities for two displays in DisplayWindowSettings
final DisplayContent dc = createMockSimulatedDisplay();
- mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay, 123, 0 /* userId */);
- mDisplayWindowSettings.setForcedDensity(dc, 456, 0 /* userId */);
+ mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay.getDisplayInfo(), 123,
+ 0 /* userId */);
+ mDisplayWindowSettings.setForcedDensity(dc.getDisplayInfo(), 456, 0 /* userId */);
// Apply settings to displays - the settings will be stored, but config will not be
// recalculated immediately.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index bf1d1fa..d31ae6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -203,6 +203,7 @@
}
final int displayId = SystemServicesTestRule.sNextDisplayId++;
+ mInfo.displayId = displayId;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
final TestDisplayContent newDisplay = createInternal(display);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index c735be0..522f9e7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -48,7 +48,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
new file mode 100644
index 0000000..70443d8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseImeAutoOpenWindowToAppTestCfArm(flicker: FlickerTest) :
+ CloseImeAutoOpenWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 4024f56..2081424 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -48,7 +48,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
new file mode 100644
index 0000000..2ffc757
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CloseImeAutoOpenWindowToHomeTestCfArm(flicker: FlickerTest) :
+ CloseImeAutoOpenWindowToHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 098c082..13feeb1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -43,7 +43,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
new file mode 100644
index 0000000..f40f5e6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseImeWindowToAppTestCfArm(flicker: FlickerTest) : CloseImeWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index f110e54..840575a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -41,7 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
new file mode 100644
index 0000000..87d9abd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 4891901..6685cf7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -72,7 +72,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
private val initializeApp = ImeStateInitializeHelper(instrumentation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
new file mode 100644
index 0000000..04cae53
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LaunchAppShowImeOnStartTestCfArm(flicker: FlickerTest) :
+ LaunchAppShowImeOnStartTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 0e732b5..d41843f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -45,7 +45,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
new file mode 100644
index 0000000..0e6cb53
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowFromFixedOrientationAppTestCfArm(flicker: FlickerTest) :
+ OpenImeWindowFromFixedOrientationAppTest(flicker)