Merge "Clean up ADB commands for letterbox and display API sandboxing." into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 1b9cf26..7393bcd 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -27,7 +27,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -282,15 +282,14 @@
public static final long ENABLE_USE_EXACT_ALARM = 218533173L;
/**
- * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} or above, the permission
- * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the user explicitly
- * allows it from Settings.
+ * The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the
+ * user explicitly allows it from Settings.
*
- * TODO (b/226439802): change to EnabledSince(T) after SDK finalization.
+ * TODO (b/226439802): Either enable it in the next SDK or replace it with a better alternative.
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
+ @Disabled
public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L;
@UnsupportedAppUsage
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 2ea8592..7b77e86 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -565,9 +565,6 @@
@VisibleForTesting
static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
"kill_on_schedule_exact_alarm_revoked";
- @VisibleForTesting
- static final String KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT =
- "schedule_exact_alarm_denied_by_default";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -612,8 +609,6 @@
private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true;
- private static final boolean DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true;
-
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -701,14 +696,6 @@
public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED;
- /**
- * When this is {@code true}, apps with the change
- * {@link AlarmManager#SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT} enabled will not get
- * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} unless the user grants it to them.
- */
- public volatile boolean SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT =
- DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT;
-
public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1;
private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -892,15 +879,6 @@
KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
break;
- case KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT:
- final boolean oldValue = SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT;
-
- SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = properties.getBoolean(
- KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT,
- DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT);
- handleScheduleExactAlarmDeniedByDefaultChange(oldValue,
- SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT);
- break;
default:
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
// The quotas need to be updated in order, so we can't just rely
@@ -971,15 +949,6 @@
}
}
- private void handleScheduleExactAlarmDeniedByDefaultChange(boolean oldValue,
- boolean newValue) {
- if (oldValue == newValue) {
- return;
- }
- mHandler.obtainMessage(AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE,
- newValue).sendToTarget();
- }
-
private void migrateAlarmsToNewStoreLocked() {
final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore()
: new BatchingAlarmStore();
@@ -1156,9 +1125,6 @@
pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
pw.println();
- pw.print(KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT,
- SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT);
- pw.println();
pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY);
pw.println();
@@ -2928,10 +2894,8 @@
}
private boolean isScheduleExactAlarmDeniedByDefault(String packageName, int userId) {
- return mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT
- && CompatChanges.isChangeEnabled(
- AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, packageName,
- UserHandle.of(userId));
+ return CompatChanges.isChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT,
+ packageName, UserHandle.of(userId));
}
@NeverCompile // Avoid size overhead of debugging code.
@@ -4707,7 +4671,6 @@
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
public static final int TARE_AFFORDABILITY_CHANGED = 12;
public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
- public static final int CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE = 14;
AlarmHandler() {
super(Looper.myLooper());
@@ -4827,32 +4790,6 @@
removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */false);
}
break;
- case CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE:
- final boolean defaultDenied = (Boolean) msg.obj;
-
- final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
- for (int appId : mExactAlarmCandidates) {
- for (int userId : startedUserIds) {
- uid = UserHandle.getUid(userId, appId);
-
- final AndroidPackage packageForUid =
- mPackageManagerInternal.getPackage(uid);
- if (packageForUid == null) {
- continue;
- }
- final String pkg = packageForUid.getPackageName();
- if (defaultDenied) {
- if (!hasScheduleExactAlarmInternal(pkg, uid)
- && !hasUseExactAlarmInternal(pkg, uid)) {
- removeExactAlarmsOnPermissionRevoked(uid, pkg, true);
- }
- } else if (hasScheduleExactAlarmInternal(pkg, uid)) {
- sendScheduleExactAlarmPermissionStateChangedBroadcast(pkg,
- UserHandle.getUserId(uid));
- }
- }
- }
- break;
default:
// nope, just ignore it
break;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d6a067d..36e1c94 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -314,7 +314,6 @@
public final class NotificationChannel implements android.os.Parcelable {
method public int getOriginalImportance();
method public boolean isImportanceLockedByCriticalDeviceFunction();
- method public boolean isImportanceLockedByOEM();
method public void lockFields(int);
method public void setDeleted(boolean);
method public void setDeletedTimeMs(long);
@@ -800,6 +799,7 @@
method public boolean hasRequestForegroundServiceExemption();
method public boolean isPrivilegedApp();
method public boolean isSystemApp();
+ method public void setEnableOnBackInvokedCallback(boolean);
field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
field public int privateFlags;
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index f081a43..7f9485b 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -15,6 +15,14 @@
"**/*.java",
"**/*.aidl",
],
+ exclude_srcs: [
+ // Remove election toolbar code from build time
+ "android/service/selectiontoolbar/*.aidl",
+ "android/service/selectiontoolbar/*.java",
+ "android/view/selectiontoolbar/*.aidl",
+ "android/view/selectiontoolbar/*.java",
+ "com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java",
+ ],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index abd6017..5d1d225 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4617,8 +4617,8 @@
try {
getService().broadcastIntentWithFeature(
null, null, intent, null, null, Activity.RESULT_OK, null, null,
- null /*requiredPermissions*/, null /*excludedPermissions*/, appOp, null, false,
- true, userId);
+ null /*requiredPermissions*/, null /*excludedPermissions*/,
+ null /*excludedPackages*/, appOp, null, false, true, userId);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 49a5c9f..f2ea060 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -568,14 +568,15 @@
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
/**
- * Checks if there is an unfinished instrumentation that targets the given uid.
+ * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+ * targets the given uid.
*
* @param uid The uid to be checked for
*
- * @return True, if there is an instrumentation whose target application uid matches the given
- * uid, false otherwise
+ * @return the uid of the instrumentation source, if there is an instrumentation whose target
+ * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
*/
- public abstract boolean isUidCurrentlyInstrumented(int uid);
+ public abstract int getInstrumentationSourceUid(int uid);
/** Is this a device owner app? */
public abstract boolean isDeviceOwner(int uid);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index c7c654a..a216021 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1335,9 +1335,17 @@
public static final int OP_ACCESS_RESTRICTED_SETTINGS =
AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+ /**
+ * Receive microphone audio from an ambient sound detection event
+ *
+ * @hide
+ */
+ public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO =
+ AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 120;
+ public static final int _NUM_OP = 121;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1800,6 +1808,14 @@
public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
"android:access_restricted_settings";
+ /**
+ * Receive microphone audio from an ambient sound detection event
+ *
+ * @hide
+ */
+ public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO =
+ "android:receive_ambient_trigger_audio";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2021,6 +2037,7 @@
OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE
OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER
OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -2147,6 +2164,7 @@
OPSTR_ESTABLISH_VPN_SERVICE,
OPSTR_ESTABLISH_VPN_MANAGER,
OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
};
/**
@@ -2274,6 +2292,7 @@
"ESTABLISH_VPN_SERVICE",
"ESTABLISH_VPN_MANAGER",
"ACCESS_RESTRICTED_SETTINGS",
+ "RECEIVE_SOUNDTRIGGER_AUDIO",
};
/**
@@ -2402,6 +2421,7 @@
null, // no permission for OP_ESTABLISH_VPN_SERVICE
null, // no permission for OP_ESTABLISH_VPN_MANAGER
null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
+ null, // no permission for OP_RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -2529,7 +2549,8 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
- null, // ACCESS_RESTRICTED_SETTINGS,
+ null, // ACCESS_RESTRICTED_SETTINGS
+ null, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -2537,8 +2558,8 @@
* restriction} for a certain app-op.
*/
private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] {
- null, //COARSE_LOCATION
- null, //FINE_LOCATION
+ new RestrictionBypass(true, false, false), //COARSE_LOCATION
+ new RestrictionBypass(true, false, false), //FINE_LOCATION
null, //GPS
null, //VIBRATE
null, //READ_CONTACTS
@@ -2547,7 +2568,7 @@
null, //WRITE_CALL_LOG
null, //READ_CALENDAR
null, //WRITE_CALENDAR
- new RestrictionBypass(true, false), //WIFI_SCAN
+ new RestrictionBypass(false, true, false), //WIFI_SCAN
null, //POST_NOTIFICATION
null, //NEIGHBORING_CELLS
null, //CALL_PHONE
@@ -2561,10 +2582,10 @@
null, //READ_ICC_SMS
null, //WRITE_ICC_SMS
null, //WRITE_SETTINGS
- new RestrictionBypass(true, false), //SYSTEM_ALERT_WINDOW
+ new RestrictionBypass(false, true, false), //SYSTEM_ALERT_WINDOW
null, //ACCESS_NOTIFICATIONS
null, //CAMERA
- new RestrictionBypass(false, true), //RECORD_AUDIO
+ new RestrictionBypass(false, false, true), //RECORD_AUDIO
null, //PLAY_AUDIO
null, //READ_CLIPBOARD
null, //WRITE_CLIPBOARD
@@ -2582,7 +2603,7 @@
null, //MONITOR_HIGH_POWER_LOCATION
null, //GET_USAGE_STATS
null, //MUTE_MICROPHONE
- new RestrictionBypass(true, false), //TOAST_WINDOW
+ new RestrictionBypass(false, true, false), //TOAST_WINDOW
null, //PROJECT_MEDIA
null, //ACTIVATE_VPN
null, //WALLPAPER
@@ -2614,7 +2635,7 @@
null, // ACCEPT_HANDOVER
null, // MANAGE_IPSEC_HANDOVERS
null, // START_FOREGROUND
- new RestrictionBypass(true, false), // BLUETOOTH_SCAN
+ new RestrictionBypass(false, true, false), // BLUETOOTH_SCAN
null, // USE_BIOMETRIC
null, // ACTIVITY_RECOGNITION
null, // SMS_FINANCIAL_TRANSACTIONS
@@ -2656,7 +2677,8 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
- null, // ACCESS_RESTRICTED_SETTINGS,
+ null, // ACCESS_RESTRICTED_SETTINGS
+ null, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -2765,7 +2787,7 @@
AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
- AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
+ AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_HOTWORD
AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS
AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
@@ -2783,6 +2805,7 @@
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
+ AppOpsManager.MODE_ALLOWED, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -2913,6 +2936,7 @@
false, // OP_ESTABLISH_VPN_SERVICE
false, // OP_ESTABLISH_VPN_MANAGER
true, // ACCESS_RESTRICTED_SETTINGS
+ false, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -3040,6 +3064,7 @@
false, // OP_ESTABLISH_VPN_SERVICE
false, // OP_ESTABLISH_VPN_MANAGER
true, // ACCESS_RESTRICTED_SETTINGS
+ false, // RECEIVE_SOUNDTRIGGER_AUDIO
};
/**
@@ -3331,6 +3356,9 @@
* @hide
*/
public static class RestrictionBypass {
+ /** Does the app need to be system uid to bypass the restriction */
+ public boolean isSystemUid;
+
/** Does the app need to be privileged to bypass the restriction */
public boolean isPrivileged;
@@ -3340,12 +3368,14 @@
*/
public boolean isRecordAudioRestrictionExcept;
- public RestrictionBypass(boolean isPrivileged, boolean isRecordAudioRestrictionExcept) {
+ public RestrictionBypass(boolean isSystemUid, boolean isPrivileged,
+ boolean isRecordAudioRestrictionExcept) {
+ this.isSystemUid = isSystemUid;
this.isPrivileged = isPrivileged;
this.isRecordAudioRestrictionExcept = isRecordAudioRestrictionExcept;
}
- public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(true, true);
+ public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(false, true, true);
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ac46066..6d982ced 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1193,7 +1193,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, null, false, false, getUserId());
+ null, AppOpsManager.OP_NONE, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1210,7 +1210,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1226,7 +1226,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1243,8 +1243,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
- getUserId());
+ null /*excludedPermissions=*/, null /*excludedPackages*/,
+ AppOpsManager.OP_NONE, options, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1259,7 +1259,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false,
user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1268,7 +1268,7 @@
@Override
public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions,
- String[] excludedPermissions) {
+ String[] excludedPermissions, String[] excludedPackages) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
@@ -1276,7 +1276,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions, excludedPermissions,
- AppOpsManager.OP_NONE, null, false, false, getUserId());
+ excludedPackages, AppOpsManager.OP_NONE, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1303,7 +1303,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- excludedPermissions, AppOpsManager.OP_NONE, options, false, false,
+ excludedPermissions, null, AppOpsManager.OP_NONE, options, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1321,7 +1321,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, appOp, null, false, false, getUserId());
+ null /*excludedPermissions=*/, null, appOp, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1338,7 +1338,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, false,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1402,7 +1402,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, receiverPermissions,
- null /*excludedPermissions=*/, appOp, options, true, false, getUserId());
+ null /*excludedPermissions=*/, null, appOp, options, true, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1416,7 +1416,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
+ null, AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1439,8 +1439,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
- user.getIdentifier());
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, options, false,
+ false, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1457,7 +1457,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, appOp, null, false, false, user.getIdentifier());
+ null /*excludedPermissions=*/, null, appOp, null, false, false,
+ user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1508,7 +1509,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, receiverPermissions,
- null /*excludedPermissions=*/, appOp, options, true, false,
+ null /*excludedPermissions=*/, null, appOp, options, true, false,
user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1550,7 +1551,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, null, false, true, getUserId());
+ null, AppOpsManager.OP_NONE, null, false, true, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1589,7 +1590,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, options, false, true, getUserId());
+ null, AppOpsManager.OP_NONE, options, false, true, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1625,7 +1626,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, null,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, true,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1658,7 +1659,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, null, false, true, user.getIdentifier());
+ null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1673,7 +1674,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
- AppOpsManager.OP_NONE, options, false, true, user.getIdentifier());
+ null, AppOpsManager.OP_NONE, options, false, true, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1708,7 +1709,7 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, null,
- null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, true,
user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 4efe9df..8367441 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -141,7 +141,7 @@
int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId,
in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode,
in String resultData, in Bundle map, in String[] requiredPermissions, in String[] excludePermissions,
- int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
+ in String[] excludePackages, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId);
@UnsupportedAppUsage
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 99d7c63..8984c42 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,10 +1058,11 @@
}
/**
- * Sends the key events corresponding to the text to the app being
- * instrumented.
- *
- * @param text The text to be sent.
+ * Sends the key events that result in the given text being typed into the currently focused
+ * window, and waits for it to be processed.
+ *
+ * @param text The text to be sent.
+ * @see #sendKeySync(KeyEvent)
*/
public void sendStringSync(String text) {
if (text == null) {
@@ -1084,11 +1085,12 @@
}
/**
- * Send a key event to the currently focused window/view and wait for it to
- * be processed. Finished at some point after the recipient has returned
- * from its event processing, though it may <em>not</em> have completely
- * finished reacting from the event -- for example, if it needs to update
- * its display as a result, it may still be in the process of doing that.
+ * Sends a key event to the currently focused window, and waits for it to be processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event The event to send to the current focus.
*/
@@ -1116,34 +1118,42 @@
}
/**
- * Sends an up and down key event sync to the currently focused window.
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
*
- * @param key The integer keycode for the event.
+ * @param keyCode The key code for the events to send.
+ * @see #sendKeySync(KeyEvent)
*/
- public void sendKeyDownUpSync(int key) {
- sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
- sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
- }
-
- /**
- * Higher-level method for sending both the down and up key events for a
- * particular character key code. Equivalent to creating both KeyEvent
- * objects by hand and calling {@link #sendKeySync}. The event appears
- * as if it came from keyboard 0, the built in one.
- *
- * @param keyCode The key code of the character to send.
- */
- public void sendCharacterSync(int keyCode) {
+ public void sendKeyDownUpSync(int keyCode) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
-
+
/**
- * Dispatch a pointer event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
+ * <p>
+ * Equivalent to {@link #sendKeyDownUpSync(int)}.
+ *
+ * @param keyCode The key code of the character to send.
+ * @see #sendKeySync(KeyEvent)
+ */
+ public void sendCharacterSync(int keyCode) {
+ sendKeyDownUpSync(keyCode);
+ }
+
+ /**
+ * Dispatches a pointer event into a window owned by the instrumented application, and waits for
+ * it to be processed.
+ * <p>
+ * If the motion event being injected is targeted at a window that is not owned by the
+ * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
+ * injecting events into all windows.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event A motion event describing the pointer action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
@@ -1155,10 +1165,10 @@
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- syncInputTransactionsAndInjectEvent(event);
+ syncInputTransactionsAndInjectEventIntoSelf(event);
}
- private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
+ private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1169,8 +1179,9 @@
.syncInputTransactions(true /*waitForAnimations*/);
}
+ // Direct the injected event into windows owned by the instrumentation target.
InputManager.getInstance().injectInputEvent(
- event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH, Process.myUid());
if (syncAfter) {
WindowManagerGlobal.getWindowManagerService()
@@ -1182,19 +1193,21 @@
}
/**
- * Dispatch a trackball event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
- *
+ * Dispatches a trackball event into the currently focused window, and waits for it to be
+ * processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
+ *
* @param event A motion event describing the trackball action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendTrackballEventSync(MotionEvent event) {
validateNotAppThread();
- if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
+ if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 436eac3..7c83d58 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -45,7 +45,9 @@
* referenced in the manifest via {@code android:localeConfig} on
* {@code <application>}.
*
- * For more information, see TODO(b/214154050): add link to guide
+ * <p>For more information, see
+ * <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
+ * the section on per-app language preferences</a>.
*
* @attr ref android.R.styleable#LocaleConfig_Locale_name
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 91ab19b..6f0b03a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -867,14 +867,6 @@
* @hide
*/
@TestApi
- public boolean isImportanceLockedByOEM() {
- return mImportanceLockedByOEM;
- }
-
- /**
- * @hide
- */
- @TestApi
public boolean isImportanceLockedByCriticalDeviceFunction() {
return mImportanceLockedDefaultApp;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6615374..4019283 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -230,8 +230,6 @@
import android.view.contentcapture.IContentCaptureManager;
import android.view.displayhash.DisplayHashManager;
import android.view.inputmethod.InputMethodManager;
-import android.view.selectiontoolbar.ISelectionToolbarManager;
-import android.view.selectiontoolbar.SelectionToolbarManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
import android.view.translation.ITranslationManager;
@@ -365,15 +363,6 @@
return new TextClassificationManager(ctx);
}});
- registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
- new CachedServiceFetcher<SelectionToolbarManager>() {
- @Override
- public SelectionToolbarManager createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
- return new SelectionToolbarManager(ctx.getOuterContext(),
- ISelectionToolbarManager.Stub.asInterface(b));
- }});
-
registerService(Context.FONT_SERVICE, FontManager.class,
new CachedServiceFetcher<FontManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 315bd71..63cdfe6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1811,10 +1811,6 @@
* #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
* #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
*
- * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
- * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
- * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
- *
* <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_PERSONALLY_OWNED}. In
* that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have
* the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -1834,6 +1830,10 @@
* activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
* contain only {@link #PROVISIONING_MODE_MANAGED_PROFILE}.
*
+ * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
+ * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
+ * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
+ *
* <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED}. In
* that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have the
* {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -13249,8 +13249,9 @@
* Called by a device owner, profile owner of a managed profile or delegated app with
* {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
*
- * <p> When network logging is enabled by a profile owner, the network logs will only include
- * work profile network activity, not activity on the personal profile.
+ * <p> Supported for a device owner from Android 8. Supported for a profile owner of a managed
+ * profile from Android 12. When network logging is enabled by a profile owner, the network logs
+ * will only include work profile network activity, not activity on the personal profile.
*
* <p> Network logs contain DNS lookup and connect() library call events. The following library
* functions are recorded while network logging is active:
@@ -14568,12 +14569,13 @@
}
/**
- * Called by Device owner to disable user control over apps. User will not be able to clear
- * app data or force-stop packages.
+ * Called by a device owner or a profile owner to disable user control over apps. User will not
+ * be able to clear app data or force-stop packages. When called by a device owner, applies to
+ * all users on the device.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param packages The package names for the apps.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner or a profile owner.
*/
public void setUserControlDisabledPackages(@NonNull ComponentName admin,
@NonNull List<String> packages) {
@@ -14588,12 +14590,14 @@
}
/**
- * Returns the list of packages over which user control is disabled by the device owner.
+ * Returns the list of packages over which user control is disabled by a device or profile
+ * owner.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
*/
- public @NonNull List<String> getUserControlDisabledPackages(@NonNull ComponentName admin) {
+ @NonNull
+ public List<String> getUserControlDisabledPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getUserControlDisabledPackages");
if (mService != null) {
try {
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index c8033fa..5044245 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -184,6 +184,12 @@
PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK";
/**
+ * Text shown on the CTA link shown to user to set a separate lock for work apps
+ */
+ public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK_ACTION =
+ PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK_ACTION";
+
+ /**
* Message shown in screen lock picker for setting up a work profile screen lock
*/
public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE =
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 4c1a363..ab48791 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -41,6 +41,7 @@
import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
+import static com.android.internal.widget.PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC;
import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE;
import android.annotation.IntDef;
@@ -569,21 +570,15 @@
result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH));
}
- // A flag indicating whether the provided password already has non-numeric characters in
- // it or if the admin imposes the requirement of any non-numeric characters.
- final boolean hasOrWouldNeedNonNumeric =
- actualMetrics.nonNumeric > 0 || adminMetrics.nonNumeric > 0
- || adminMetrics.letters > 0 || adminMetrics.lowerCase > 0
- || adminMetrics.upperCase > 0 || adminMetrics.symbols > 0;
- final PasswordMetrics minMetrics =
- applyComplexity(adminMetrics, hasOrWouldNeedNonNumeric, bucket);
+ final PasswordMetrics minMetrics = applyComplexity(adminMetrics,
+ actualMetrics.credType == CREDENTIAL_TYPE_PIN, bucket);
// Clamp required length between maximum and minimum valid values.
minMetrics.length = Math.min(MAX_PASSWORD_LENGTH,
Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE));
minMetrics.removeOverlapping();
- comparePasswordMetrics(minMetrics, actualMetrics, result);
+ comparePasswordMetrics(minMetrics, bucket, actualMetrics, result);
return result;
}
@@ -591,11 +586,23 @@
/**
* TODO: move to PasswordPolicy
*/
- private static void comparePasswordMetrics(PasswordMetrics minMetrics,
+ private static void comparePasswordMetrics(PasswordMetrics minMetrics, ComplexityBucket bucket,
PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) {
if (actualMetrics.length < minMetrics.length) {
result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length));
}
+ if (actualMetrics.nonNumeric == 0 && minMetrics.nonNumeric == 0 && minMetrics.letters == 0
+ && minMetrics.lowerCase == 0 && minMetrics.upperCase == 0
+ && minMetrics.symbols == 0) {
+ // When provided password is all numeric and all numeric password is allowed.
+ int allNumericMinimumLength = bucket.getMinimumLength(false);
+ if (allNumericMinimumLength > minMetrics.length
+ && allNumericMinimumLength > minMetrics.numeric
+ && actualMetrics.length < allNumericMinimumLength) {
+ result.add(new PasswordValidationError(
+ TOO_SHORT_WHEN_ALL_NUMERIC, allNumericMinimumLength));
+ }
+ }
if (actualMetrics.letters < minMetrics.letters) {
result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters));
}
@@ -668,15 +675,12 @@
*
* TODO: move to PasswordPolicy
*/
- public static PasswordMetrics applyComplexity(
- PasswordMetrics adminMetrics, boolean withNonNumericCharacters,
+ public static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin,
int complexity) {
- return applyComplexity(adminMetrics, withNonNumericCharacters,
- ComplexityBucket.forComplexity(complexity));
+ return applyComplexity(adminMetrics, isPin, ComplexityBucket.forComplexity(complexity));
}
- private static PasswordMetrics applyComplexity(
- PasswordMetrics adminMetrics, boolean withNonNumericCharacters,
+ private static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin,
ComplexityBucket bucket) {
final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics);
@@ -684,8 +688,7 @@
minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE);
}
- minMetrics.length = Math.max(minMetrics.length,
- bucket.getMinimumLength(withNonNumericCharacters));
+ minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin));
return minMetrics;
}
diff --git a/core/java/android/app/admin/ProvisioningIntentHelper.java b/core/java/android/app/admin/ProvisioningIntentHelper.java
index fbad90c..1c38559 100644
--- a/core/java/android/app/admin/ProvisioningIntentHelper.java
+++ b/core/java/android/app/admin/ProvisioningIntentHelper.java
@@ -17,8 +17,10 @@
package android.app.admin;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_NFC;
@@ -36,12 +38,14 @@
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.util.Log;
import java.io.IOException;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.Properties;
+import java.util.Set;
/**
* Utility class that provides functionality to create provisioning intents from nfc intents.
@@ -124,12 +128,46 @@
ComponentName componentName = ComponentName.unflattenFromString(
properties.getProperty(propertyName));
bundle.putParcelable(propertyName, componentName);
+ } else if (EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE.equals(propertyName)
+ || EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE.equals(propertyName)) {
+ try {
+ bundle.putParcelable(propertyName,
+ deserializeExtrasBundle(properties, propertyName));
+ } catch (IOException e) {
+ Log.e(TAG,
+ "Failed to parse " + propertyName + ".", e);
+ }
}
else {
bundle.putString(propertyName, properties.getProperty(propertyName));
}
}
+ /**
+ * Get a {@link PersistableBundle} from a {@code String} property in a {@link Properties}
+ * object.
+ * @param properties the source of the extra
+ * @param extraName key into the {@link Properties} object
+ * @return the {@link PersistableBundle} or {@code null} if there was no property with the
+ * given name
+ * @throws IOException if there was an error parsing the property
+ */
+ private static PersistableBundle deserializeExtrasBundle(
+ Properties properties, String extraName) throws IOException {
+ String serializedExtras = properties.getProperty(extraName);
+ if (serializedExtras == null) {
+ return null;
+ }
+ Properties bundleProperties = new Properties();
+ bundleProperties.load(new StringReader(serializedExtras));
+ PersistableBundle extrasBundle = new PersistableBundle(bundleProperties.size());
+ Set<String> propertyNames = bundleProperties.stringPropertyNames();
+ for (String propertyName : propertyNames) {
+ extrasBundle.putString(propertyName, bundleProperties.getProperty(propertyName));
+ }
+ return extrasBundle;
+ }
+
private static Intent createProvisioningIntentFromBundle(Bundle bundle) {
requireNonNull(bundle);
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index be077435..79d7b21 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -174,7 +174,7 @@
public static final int FEATURE_MEDIA_HEADS_UP = 36;
public static final int FEATURE_STEP_COUNTING = 37;
public static final int FEATURE_EARTHQUAKE_ALERT = 38;
- public static final int FEATURE_STEP_DATE = 39;
+ public static final int FEATURE_STEP_DATE = 39; // This represents a DATE. "STEP" is a typo.
public static final int FEATURE_BLAZE_BUILD_PROGRESS = 40;
public static final int FEATURE_EARTHQUAKE_OCCURRED = 41;
@@ -283,7 +283,7 @@
this.mAssociatedSmartspaceTargetId = in.readString();
this.mSliceUri = in.readTypedObject(Uri.CREATOR);
this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
- this.mTemplateData = in.readTypedObject(BaseTemplateData.CREATOR);
+ this.mTemplateData = in.readParcelable(/* loader= */null, BaseTemplateData.class);
}
private SmartspaceTarget(String smartspaceTargetId,
@@ -491,7 +491,7 @@
dest.writeString(this.mAssociatedSmartspaceTargetId);
dest.writeTypedObject(this.mSliceUri, flags);
dest.writeTypedObject(this.mWidget, flags);
- dest.writeTypedObject(this.mTemplateData, flags);
+ dest.writeParcelable(this.mTemplateData, flags);
}
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 907db7d..836bff5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2269,6 +2269,19 @@
*/
public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
@NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+ sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions, null);
+ }
+
+
+ /**
+ * Like {@link #sendBroadcastMultiplePermissions(Intent, String[], String[])}, but also allows
+ * specification of a list of excluded packages.
+ *
+ * @hide
+ */
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions,
+ @Nullable String[] excludedPackages) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 4ecd776..e654918 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -515,8 +515,10 @@
/** @hide */
@Override
public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
- @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
- mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions);
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions,
+ @Nullable String[] excludedPackages) {
+ mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions,
+ excludedPackages);
}
/** @hide */
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 1b84686b..fb41b89 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -413,7 +413,10 @@
final int iconResId = (int) getPropertyLong(KEY_ICON_RES_ID);
final String iconResName = getPropertyString(KEY_ICON_RES_NAME);
final String iconUri = getPropertyString(KEY_ICON_URI);
- final int disabledReason = Integer.parseInt(getPropertyString(KEY_DISABLED_REASON));
+ final String disabledReasonString = getPropertyString(KEY_DISABLED_REASON);
+ final int disabledReason = !TextUtils.isEmpty(disabledReasonString)
+ ? Integer.parseInt(getPropertyString(KEY_DISABLED_REASON))
+ : ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
final Map<String, Map<String, List<String>>> capabilityBindings =
parseCapabilityBindings(getPropertyStringArray(KEY_CAPABILITY_BINDINGS));
return new ShortcutInfo(
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2961b55..24c3836 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -2752,4 +2752,21 @@
mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
}
}
+
+ /**
+ * Sets whether the application will use the {@link android.window.OnBackInvokedCallback}
+ * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related
+ * callbacks. Intended to be used from tests only.
+ *
+ * @see #isOnBackInvokedCallbackEnabled()
+ * @hide
+ */
+ @TestApi
+ public void setEnableOnBackInvokedCallback(boolean isEnable) {
+ if (isEnable) {
+ privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+ } else {
+ privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d..52e64e8 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2618,6 +2618,15 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
@@ -2689,6 +2698,15 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index cb55e30..20a4fdf 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -567,9 +567,14 @@
targetCode = minCode;
}
+ boolean allowUnknownCodenames = false;
+ if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) {
+ allowUnknownCodenames = true;
+ }
+
ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
targetVer, targetCode, SDK_CODENAMES, input,
- /* allowUnknownCodenames= */ false);
+ allowUnknownCodenames);
if (targetResult.isError()) {
return input.error(targetResult);
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 6d74b81..8cc4cdb 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -58,6 +58,7 @@
private static final int MAX_FILE_NAME_SIZE = 223;
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
+ public static final int PARSE_APK_IN_APEX = 1 << 9;
/**
* Check if the given name is valid.
@@ -315,6 +316,15 @@
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Parsed package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -367,16 +377,29 @@
return input.success(targetVers);
}
- if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
- return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
-
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Parsed package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
+ try {
+ if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+ } catch (IllegalArgumentException e) {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1263da6..4d0ba63 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -72,7 +72,7 @@
import java.util.concurrent.Executor;
public final class CameraExtensionSessionImpl extends CameraExtensionSession {
- private static final int PREVIEW_QUEUE_SIZE = 3;
+ private static final int PREVIEW_QUEUE_SIZE = 10;
private static final String TAG = "CameraExtensionSessionImpl";
private final Executor mExecutor;
@@ -1057,15 +1057,8 @@
mClientRequest));
if (mCaptureResultHandler != null) {
- CameraMetadataNative captureResults = new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1127,6 +1120,11 @@
private class ImageCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ notifyCaptureFailed();
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mCaptureFailed) {
img.close();
@@ -1160,6 +1158,9 @@
private class ImageLoopbackCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) { }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
img.close();
}
@@ -1221,7 +1222,8 @@
}
private interface OnImageAvailableListener {
- public void onImageAvailable (ImageReader reader, Image img);
+ void onImageDropped(long timestamp);
+ void onImageAvailable (ImageReader reader, Image img);
}
private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener,
@@ -1263,6 +1265,29 @@
} else {
mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null));
}
+
+ notifyDroppedImages(timestamp);
+ }
+
+ private void notifyDroppedImages(long timestamp) {
+ Set<Long> timestamps = mImageListenerMap.keySet();
+ ArrayList<Long> removedTs = new ArrayList<>();
+ for (long ts : timestamps) {
+ if (ts < timestamp) {
+ Log.e(TAG, "Dropped image with ts: " + ts);
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
+ if (entry.second != null) {
+ entry.second.onImageDropped(ts);
+ }
+ if (entry.first != null) {
+ entry.first.close();
+ }
+ removedTs.add(ts);
+ }
+ }
+ for (long ts : removedTs) {
+ mImageListenerMap.remove(ts);
+ }
}
public void registerListener(Long timestamp, OnImageAvailableListener listener) {
@@ -1291,6 +1316,12 @@
entry.first.close();
}
}
+ for (long timestamp : mImageListenerMap.keySet()) {
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp);
+ if (entry.second != null) {
+ entry.second.onImageDropped(timestamp);
+ }
+ }
mImageListenerMap.clear();
}
}
@@ -1447,7 +1478,6 @@
} else {
notifyConfigurationFailure();
}
-
}
@Override
@@ -1536,7 +1566,16 @@
} else if (mPreviewProcessorType ==
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
int idx = mPendingResultMap.indexOfKey(timestamp);
- if (idx >= 0) {
+
+ if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
+ // Image was dropped before we can receive the capture results
+ if ((mCaptureResultHandler != null)) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
+ discardPendingRepeatingResults(idx, mPendingResultMap, false);
+ } else if (idx >= 0) {
+ // Image came before the capture results
ParcelImage parcelImage = initializeParcelImage(
mPendingResultMap.get(timestamp).first);
try {
@@ -1563,6 +1602,7 @@
}
discardPendingRepeatingResults(idx, mPendingResultMap, false);
} else {
+ // Image not yet available
notifyClient = false;
mPendingResultMap.put(timestamp,
new Pair<>(null,
@@ -1581,16 +1621,8 @@
mClientRequest));
if ((mCaptureResultHandler != null) && (mPreviewProcessorType !=
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) {
- CameraMetadataNative captureResults =
- new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} else {
mExecutor.execute(
@@ -1657,19 +1689,24 @@
for (int i = idx; i >= 0; i--) {
if (previewMap.valueAt(i).first != null) {
previewMap.valueAt(i).first.close();
- } else {
- if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
- Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(
- () -> mCallbacks
- .onCaptureFailed(CameraExtensionSessionImpl.this,
- mClientRequest));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) &&
+ ((i != idx) || notifyCurrentIndex)) {
+ TotalCaptureResult result = previewMap.valueAt(i).second;
+ Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+
+ Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+
}
previewMap.removeAt(i);
}
@@ -1683,6 +1720,12 @@
}
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (img == null) {
Log.e(TAG, "Invalid image!");
@@ -1703,6 +1746,15 @@
private class ImageProcessCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ // Add an empty frame&results entry to flag that we dropped a frame
+ // and valid capture results can immediately return to client.
+ mPendingResultMap.put(timestamp, new Pair<>(null, null));
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) {
// We reached the maximum acquired images limit. This is possible in case we
@@ -1768,6 +1820,17 @@
}
}
+ private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) {
+ CameraMetadataNative captureResults = new CameraMetadataNative();
+ for (CaptureResult.Key key : mSupportedResultKeys) {
+ Object value = result.get(key);
+ if (value != null) {
+ captureResults.set(key, value);
+ }
+ }
+ return captureResults;
+ }
+
private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
@NonNull Size arSize) {
final float TOLL = .01f;
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index e1ffd4a..2da12e6c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,11 +57,16 @@
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
- // Injects an input event into the system. To inject into windows owned by other
- // applications, the caller must have the INJECT_EVENTS permission.
+ // Injects an input event into the system. The caller must have the INJECT_EVENTS permssion.
+ // This method exists only for compatibility purposes and may be removed in a future release.
@UnsupportedAppUsage
boolean injectInputEvent(in InputEvent ev, int mode);
+ // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
+ // The caller can target windows owned by a certain UID by providing a valid UID, or by
+ // providing {@link android.os.Process#INVALID_UID} to target all windows.
+ boolean injectInputEventToTarget(in InputEvent ev, int mode, int targetUid);
+
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
// Calibrate input device position
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cc5b275..d17a952 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,6 +45,7 @@
import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,58 @@
}
}
+ /**
+ * Injects an input event into the event system, targeting windows owned by the provided uid.
+ *
+ * If a valid targetUid is provided, the system will only consider injecting the input event
+ * into windows owned by the provided uid. If the input event is targeted at a window that is
+ * not owned by the provided uid, input injection will fail and a RemoteException will be
+ * thrown.
+ *
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * <p>
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+ * </p><p>
+ * Make sure you correctly set the event time and input source of the event
+ * before calling this method.
+ * </p>
+ *
+ * @param event The event to inject.
+ * @param mode The synchronization mode. One of:
+ * {@link android.os.InputEventInjectionSync.NONE},
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+ * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
+ * windows.
+ * @return True if input event injection succeeded.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+ public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mode != InputEventInjectionSync.NONE
+ && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+ && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
+ throw new IllegalArgumentException("mode is invalid");
+ }
+
+ try {
+ return mIm.injectInputEventToTarget(event, mode, targetUid);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
/**
* Injects an input event into the event system on behalf of an application.
* The synchronization mode determines whether the method blocks while waiting for
* input injection to proceed.
* <p>
- * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
- * windows that are owned by other applications.
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
@@ -1129,22 +1174,10 @@
*
* @hide
*/
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
@UnsupportedAppUsage
public boolean injectInputEvent(InputEvent event, int mode) {
- if (event == null) {
- throw new IllegalArgumentException("event must not be null");
- }
- if (mode != InputEventInjectionSync.NONE
- && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
- && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
- throw new IllegalArgumentException("mode is invalid");
- }
-
- try {
- return mIm.injectInputEvent(event, mode);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return injectInputEvent(event, mode, Process.INVALID_UID);
}
/**
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index b37c27c..fc6bc55 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -75,8 +75,15 @@
/**
* Sets the display id that the MouseCursorController will be forced to target. Pass
* {@link android.view.Display#INVALID_DISPLAY} to clear the override.
+ *
+ * Note: This method generally blocks until the pointer display override has propagated.
+ * When setting a new override, the caller should ensure that an input device that can control
+ * the mouse pointer is connected. If a new override is set when no such input device is
+ * connected, the caller may be blocked for an arbitrary period of time.
+ *
+ * @return true if the pointer displayId was set successfully, or false if it fails.
*/
- public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
+ public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
/**
* Gets the display id that the MouseCursorController is being forced to target. Returns
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index f9ed0e3d..3f49b73 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -37,6 +37,7 @@
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
@@ -194,7 +195,9 @@
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
final IBinder startInputToken = (IBinder) args.arg1;
- final IInputContext inputContext = (IInputContext) args.arg2;
+ final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1;
+ final ImeOnBackInvokedDispatcher imeDispatcher =
+ (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2;
final EditorInfo info = (EditorInfo) args.arg3;
final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
final boolean restarting = args.argi5 == 1;
@@ -205,7 +208,7 @@
: null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
- navButtonFlags);
+ navButtonFlags, imeDispatcher);
args.recycle();
return;
}
@@ -348,13 +351,17 @@
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
EditorInfo attribute, boolean restarting,
- @InputMethodNavButtonFlags int navButtonFlags) {
+ @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = inputContext;
+ args.arg2 = imeDispatcher;
mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
- inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
+ args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index efd4f06..25296bc 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -101,6 +101,7 @@
import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
+import android.view.InputDevice;
import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -134,6 +135,9 @@
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.window.ImeOnBackInvokedDispatcher;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import android.window.WindowMetricsHelper;
import com.android.internal.annotations.GuardedBy;
@@ -344,6 +348,9 @@
* A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
**/
private RingBuffer<MotionEvent> mPendingEvents;
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
+ private Boolean mBackCallbackRegistered = false;
+ private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -797,7 +804,13 @@
@Override
public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) {
+ @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ mImeDispatcher = imeDispatcher;
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(
+ imeDispatcher);
+ }
mPrivOps.reportStartInputAsync(startInputToken);
mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
if (restarting) {
@@ -1496,6 +1509,10 @@
Context.LAYOUT_INFLATER_SERVICE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
+ if (mImeDispatcher != null) {
+ mWindow.getOnBackInvokedDispatcher()
+ .setImeOnBackInvokedDispatcher(mImeDispatcher);
+ }
mNavigationBarController.onSoftInputWindowCreated(mWindow);
{
final Window window = mWindow.getWindow();
@@ -1608,6 +1625,8 @@
// when IME developers are doing something unsupported.
InputMethodPrivilegedOperationsRegistry.remove(mToken);
}
+ unregisterCompatOnBackInvokedCallback();
+ mImeDispatcher = null;
}
/**
@@ -2568,9 +2587,47 @@
cancelImeSurfaceRemoval();
mInShowWindow = false;
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ registerCompatOnBackInvokedCallback();
}
+ /**
+ * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time
+ * back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code
+ * around to handle back on older devices.
+ */
+ private void registerCompatOnBackInvokedCallback() {
+ if (mBackCallbackRegistered) {
+ return;
+ }
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+ mBackCallbackRegistered = true;
+ }
+ }
+
+ private void unregisterCompatOnBackInvokedCallback() {
+ if (!mBackCallbackRegistered) {
+ return;
+ }
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher()
+ .unregisterOnBackInvokedCallback(mCompatBackCallback);
+ mBackCallbackRegistered = false;
+ }
+ }
+
+ private KeyEvent createBackKeyEvent(int action, boolean isTracking) {
+ final long when = SystemClock.uptimeMillis();
+ return new KeyEvent(when, when, action,
+ KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY
+ | (isTracking ? KeyEvent.FLAG_TRACKING : 0),
+ InputDevice.SOURCE_KEYBOARD);
+ }
+
private boolean prepareWindow(boolean showInput) {
boolean doShowInput = false;
mDecorViewVisible = true;
@@ -2658,6 +2715,7 @@
}
mLastWasInFullscreenMode = mIsFullscreen;
updateFullscreenMode();
+ unregisterCompatOnBackInvokedCallback();
}
/**
@@ -3797,4 +3855,14 @@
proto.end(token);
}
};
+
+ private void compatHandleBack() {
+ final KeyEvent downEvent = createBackKeyEvent(
+ KeyEvent.ACTION_DOWN, false /* isTracking */);
+ onKeyDown(KeyEvent.KEYCODE_BACK, downEvent);
+ final boolean hasStartedTracking =
+ (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+ final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
+ onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
+ }
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 3c2c7f0..b494c7f 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -860,7 +860,7 @@
Binder.restoreCallingIdentity(token);
}
}
- });
+ }, executor);
}
/**
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 4ed939c..f5f1c37 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -30,6 +30,7 @@
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
@@ -137,6 +138,7 @@
private static final List<String> MIC_OPS = List.of(
OPSTR_PHONE_CALL_MICROPHONE,
+ OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
OPSTR_RECORD_AUDIO
);
@@ -147,6 +149,7 @@
private static @NonNull String getGroupForOp(String op) {
switch (op) {
+ case OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO:
case OPSTR_RECORD_AUDIO:
return MICROPHONE;
case OPSTR_CAMERA:
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 37f44e9..9a2f7ba 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -738,6 +738,14 @@
*/
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
+ /**
+ * Namespace for DevicePolicyManager related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
+ "device_policy_manager";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dac54cf..d2a86eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9695,6 +9695,40 @@
"active_unlock_on_biometric_fail";
/**
+ * If active unlock triggers on biometric failures, include the following error codes
+ * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}.
+ * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock
+ * should never trigger on any face errors, this should be set to an empty string.
+ * A null value will use the system default value (TIMEOUT).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_FACE_ERRORS =
+ "active_unlock_on_face_errors";
+
+ /**
+ * If active unlock triggers on biometric failures, include the following acquired info
+ * as a "biometric failure". See {@link android.hardware.biometrics.BiometricFaceConstants}.
+ * Acquired codes should be separated by a pipe. For example: "1|4|5". If active unlock
+ * should never on trigger on any acquired info messages, this should be
+ * set to an empty string. A null value will use the system default value (none).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO =
+ "active_unlock_on_face_acquire_info";
+
+ /**
+ * If active unlock triggers on biometric failures, then also request active unlock on
+ * unlock intent when each setting (BiometricType) is the only biometric type enrolled.
+ * Biometric types should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (0 / None).
+ * 0 = None, 1 = Any face, 2 = Any fingerprint, 3 = Under display fingerprint
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED =
+ "active_unlock_on_unlock_intent_when_biometric_enrolled";
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -10121,15 +10155,6 @@
public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl";
/**
- * Whether the app-level notification setting is represented by a manifest permission.
- *
- * @hide
- */
- @Readable
- public static final String NOTIFICATION_PERMISSION_ENABLED =
- "notification_permission_enabled";
-
- /**
* Comma separated list of QS tiles that have been auto-added already.
* @hide
*/
diff --git a/core/java/android/service/quickaccesswallet/TEST_MAPPING b/core/java/android/service/quickaccesswallet/TEST_MAPPING
index 4d97ab6..5d2a3a8 100644
--- a/core/java/android/service/quickaccesswallet/TEST_MAPPING
+++ b/core/java/android/service/quickaccesswallet/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit": [
+ "presubmit-large": [
{
"name": "CtsQuickAccessWalletTestCases"
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0ec95c6..425dbb9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -904,6 +904,7 @@
// based on its default wallpaper color hints.
mShouldDim = dimAmount != 0f || mShouldDimByDefault;
updateSurfaceDimming();
+ updateSurface(false, false, true);
}
private void updateSurfaceDimming() {
@@ -940,7 +941,6 @@
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
- updateSurface(false, false, true);
}
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index e5ec260..691452f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -341,10 +341,12 @@
*/
@UnsupportedAppUsage
public void destroy() {
- if (mNativeObject != 0) {
- nativeDestroy(mNativeObject);
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ }
+ release();
}
- release();
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2475e2c..38ca248 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9922,7 +9922,7 @@
* <ol>
* <li>It should only be called when content capture is enabled for the view.
* <li>It must call viewAppeared() before viewDisappeared()
- * <li>viewAppearead() can only be called when the view is visible and laidout
+ * <li>viewAppeared() can only be called when the view is visible and laid out
* <li>It should not call the same event twice.
* </ol>
*/
@@ -9999,6 +9999,11 @@
Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this);
}
}
+
+ // We reset any translation state as views may be re-used (e.g., as in ListView and
+ // RecyclerView). We only need to do this for views important for content capture since
+ // views unimportant for content capture won't be translated anyway.
+ clearTranslationState();
}
}
@@ -11936,7 +11941,7 @@
@NonNull
public final List<Rect> getUnrestrictedPreferKeepClearRects() {
final ListenerInfo info = mListenerInfo;
- if (info != null && info.mKeepClearRects != null) {
+ if (info != null && info.mUnrestrictedKeepClearRects != null) {
return new ArrayList(info.mUnrestrictedKeepClearRects);
}
@@ -12719,6 +12724,21 @@
}
/**
+ * @hide
+ */
+ public void clearTranslationState() {
+ if (mViewTranslationCallback != null) {
+ mViewTranslationCallback.onClearTranslation(this);
+ }
+ clearViewTranslationCallback();
+ clearViewTranslationResponse();
+ if (hasTranslationTransientState()) {
+ setHasTransientState(false);
+ setHasTranslationTransientState(false);
+ }
+ }
+
+ /**
* Returns true if this view is currently attached to a window.
*/
public boolean isAttachedToWindow() {
@@ -21150,6 +21170,11 @@
}
AccessibilityNodeIdManager.getInstance().unregisterViewWithId(getAccessibilityViewId());
+
+ if (mBackgroundRenderNode != null) {
+ mBackgroundRenderNode.forceEndAnimators();
+ }
+ mRenderNode.forceEndAnimators();
}
private void cleanupDraw() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0bdbfbc..48c102b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6095,6 +6095,28 @@
@Override
protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ final KeyEvent event = (KeyEvent) q.mEvent;
+
+ // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
+ // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
+ if (isBack(event)
+ && mContext != null
+ && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+ OnBackInvokedCallback topCallback =
+ getOnBackInvokedDispatcher().getTopCallback();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (topCallback != null) {
+ topCallback.onBackInvoked();
+ return FINISH_HANDLED;
+ }
+ } else {
+ // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
+ return FINISH_NOT_HANDLED;
+ }
+ }
+ }
+
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
@@ -6446,24 +6468,6 @@
return FINISH_HANDLED;
}
- // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
- // view tree and invoke the appropriate {@link OnBackInvokedCallback}.
- if (isBack(event)
- && mContext != null
- && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
- OnBackInvokedCallback topCallback =
- getOnBackInvokedDispatcher().getTopCallback();
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (topCallback != null) {
- topCallback.onBackInvoked();
- return FINISH_HANDLED;
- }
- } else {
- // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
- return FINISH_NOT_HANDLED;
- }
- }
-
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5bc340b..00052f6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3108,10 +3108,14 @@
/**
* The preferred refresh rate for the window.
- *
+ * <p>
* This must be one of the supported refresh rates obtained for the display(s) the window
* is on. The selected refresh rate will be applied to the display's default mode.
- *
+ * <p>
+ * This should be used in favor of {@link LayoutParams#preferredDisplayModeId} for
+ * applications that want to specify the refresh rate, but do not want to specify a
+ * preference for any other displayMode properties (e.g., resolution).
+ * <p>
* This value is ignored if {@link #preferredDisplayModeId} is set.
*
* @see Display#getSupportedRefreshRates()
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 6209b46..dbdc0da 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -29,6 +29,7 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.View;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -232,6 +233,10 @@
* long as your implementation of {@link InputMethod} relies on such
* IPCs
* @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session.
+ * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher }} to be set on the
+ * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME
+ * {@link android.window.OnBackInvokedCallback}s can be forwarded to
+ * the client requesting to start input.
* @see #startInput(InputConnection, EditorInfo)
* @see #restartInput(InputConnection, EditorInfo)
* @see EditorInfo
@@ -240,7 +245,8 @@
@MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) {
+ @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d9bde58..e2e9a85 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -91,6 +91,8 @@
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillManager;
+import android.window.ImeOnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
@@ -105,6 +107,7 @@
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -279,6 +282,21 @@
private static final String SUBTYPE_MODE_VOICE = "voice";
/**
+ * Provide this to {@link IInputMethodManager#startInputOrWindowGainedFocus(
+ * int, IInputMethodClient, IBinder, int, int, int, EditorInfo, IInputContext, int)} to receive
+ * {@link android.window.OnBackInvokedCallback} registrations from IME.
+ */
+ private final ImeOnBackInvokedDispatcher mImeDispatcher =
+ new ImeOnBackInvokedDispatcher(Handler.getMain()) {
+ @Override
+ public WindowOnBackInvokedDispatcher getReceivingDispatcher() {
+ synchronized (mH) {
+ return mCurRootView != null ? mCurRootView.getOnBackInvokedDispatcher() : null;
+ }
+ }
+ };
+
+ /**
* Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
* or indirectly relied on {@link #sInstance} via reflection or something like that.
*
@@ -740,7 +758,8 @@
windowFlags,
null,
null, null,
- mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1687,6 +1706,8 @@
mServedConnecting = false;
clearConnectionLocked();
}
+ // Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
+ mImeDispatcher.clear();
}
public void displayCompletions(View view, CompletionInfo[] completions) {
@@ -2359,7 +2380,8 @@
softInputMode, windowFlags, tba, servedInputConnection,
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion);
+ view.getContext().getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 8cf032b..6bf2474 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -158,12 +158,7 @@
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
runForEachView((view, callback) -> {
- callback.onClearTranslation(view);
- view.clearViewTranslationResponse();
- if (view.hasTranslationTransientState()) {
- view.setHasTransientState(false);
- view.setHasTranslationTransientState(false);
- }
+ view.clearTranslationState();
});
notifyTranslationFinished(/* activityDestroyed= */ false);
synchronized (mLock) {
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.aidl b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl
new file mode 100644
index 0000000..04e6420
--- /dev/null
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/ParcelFileDescriptor.aidl
+**
+** Copyright 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.window;
+
+parcelable ImeOnBackInvokedDispatcher;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
new file mode 100644
index 0000000..d5763aa
--- /dev/null
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -0,0 +1,187 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback}
+ * registrations from the IME process to the app process to be registered on the app window.
+ * <p>
+ * The app process creates and propagates an instance of {@link ImeOnBackInvokedDispatcher}
+ * to the IME to be set on the IME window's {@link WindowOnBackInvokedDispatcher}.
+ * <p>
+ * @see WindowOnBackInvokedDispatcher#setImeOnBackInvokedDispatcher
+ *
+ * @hide
+ */
+public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parcelable {
+
+ private static final String TAG = "ImeBackDispatcher";
+ static final String RESULT_KEY_ID = "id";
+ static final String RESULT_KEY_CALLBACK = "callback";
+ static final String RESULT_KEY_PRIORITY = "priority";
+ static final int RESULT_CODE_REGISTER = 0;
+ static final int RESULT_CODE_UNREGISTER = 1;
+ @NonNull
+ private final ResultReceiver mResultReceiver;
+
+ public ImeOnBackInvokedDispatcher(Handler handler) {
+ mResultReceiver = new ResultReceiver(handler) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher();
+ if (dispatcher != null) {
+ receive(resultCode, resultData, dispatcher);
+ }
+ }
+ };
+ }
+
+ /**
+ * Override this method to return the {@link WindowOnBackInvokedDispatcher} of the window
+ * that should receive the forwarded callback.
+ */
+ @Nullable
+ protected WindowOnBackInvokedDispatcher getReceivingDispatcher() {
+ return null;
+ }
+
+ ImeOnBackInvokedDispatcher(Parcel in) {
+ mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
+ }
+
+ @Override
+ public void registerOnBackInvokedCallback(
+ @OnBackInvokedDispatcher.Priority int priority,
+ @NonNull OnBackInvokedCallback callback) {
+ final Bundle bundle = new Bundle();
+ final IOnBackInvokedCallback iCallback =
+ new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+ bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
+ bundle.putInt(RESULT_KEY_PRIORITY, priority);
+ bundle.putInt(RESULT_KEY_ID, callback.hashCode());
+ mResultReceiver.send(RESULT_CODE_REGISTER, bundle);
+ }
+
+ @Override
+ public void unregisterOnBackInvokedCallback(
+ @NonNull OnBackInvokedCallback callback) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(RESULT_KEY_ID, callback.hashCode());
+ mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mResultReceiver, flags);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR =
+ new Parcelable.Creator<ImeOnBackInvokedDispatcher>() {
+ public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) {
+ return new ImeOnBackInvokedDispatcher(in);
+ }
+ public ImeOnBackInvokedDispatcher[] newArray(int size) {
+ return new ImeOnBackInvokedDispatcher[size];
+ }
+ };
+
+ private final HashMap<Integer, OnBackInvokedCallback> mImeCallbackMap = new HashMap<>();
+
+ private void receive(
+ int resultCode, Bundle resultData,
+ @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ final int callbackId = resultData.getInt(RESULT_KEY_ID);
+ if (resultCode == RESULT_CODE_REGISTER) {
+ int priority = resultData.getInt(RESULT_KEY_PRIORITY);
+ final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface(
+ resultData.getBinder(RESULT_KEY_CALLBACK));
+ registerReceivedCallback(
+ callback, priority, callbackId, receivingDispatcher);
+ } else if (resultCode == RESULT_CODE_UNREGISTER) {
+ unregisterReceivedCallback(callbackId, receivingDispatcher);
+ }
+ }
+
+ private void registerReceivedCallback(
+ @NonNull IOnBackInvokedCallback iCallback,
+ @OnBackInvokedDispatcher.Priority int priority,
+ int callbackId,
+ @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ final ImeOnBackInvokedCallback imeCallback =
+ new ImeOnBackInvokedCallback(iCallback);
+ mImeCallbackMap.put(callbackId, imeCallback);
+ receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
+ }
+
+ private void unregisterReceivedCallback(
+ int callbackId, OnBackInvokedDispatcher receivingDispatcher) {
+ final OnBackInvokedCallback callback = mImeCallbackMap.get(callbackId);
+ if (callback == null) {
+ Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. "
+ + "callbackId: " + callbackId);
+ return;
+ }
+ receivingDispatcher.unregisterOnBackInvokedCallback(callback);
+ }
+
+ /** Clears all registered callbacks on the instance. */
+ public void clear() {
+ mImeCallbackMap.clear();
+ }
+
+ static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
+ @NonNull
+ private final IOnBackInvokedCallback mIOnBackInvokedCallback;
+
+ ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback) {
+ mIOnBackInvokedCallback = iCallback;
+ }
+
+ @Override
+ public void onBackInvoked() {
+ try {
+ if (mIOnBackInvokedCallback != null) {
+ mIOnBackInvokedCallback.onBackInvoked();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
+ }
+ }
+
+ IOnBackInvokedCallback getIOnBackInvokedCallback() {
+ return mIOnBackInvokedCallback;
+ }
+ }
+}
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index 6bc2b50..3539049a 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -96,4 +96,19 @@
* @hide
*/
default void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { }
+
+
+ /**
+ * Sets an {@link ImeOnBackInvokedDispatcher} to forward {@link OnBackInvokedCallback}s
+ * from IME to the app process to be registered on the app window.
+ *
+ * Only call this on the IME window. Create the {@link ImeOnBackInvokedDispatcher} from
+ * the application process and override
+ * {@link ImeOnBackInvokedDispatcher#getReceivingDispatcher()} to point to the app
+ * window's {@link WindowOnBackInvokedDispatcher}.
+ *
+ * @hide
+ */
+ default void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { }
}
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 4409397..bedf503 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -49,6 +49,7 @@
private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>();
private final Object mLock = new Object();
private OnBackInvokedDispatcher mActualDispatcher = null;
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
@Override
public void registerOnBackInvokedCallback(
@@ -108,6 +109,9 @@
Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(),
mActualDispatcher));
}
+ if (mImeDispatcher != null) {
+ mActualDispatcher.setImeOnBackInvokedDispatcher(mImeDispatcher);
+ }
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
if (priority >= 0) {
@@ -117,6 +121,7 @@
}
}
mCallbacks.clear();
+ mImeDispatcher = null;
}
private void clearCallbacksOnDispatcher() {
@@ -142,6 +147,7 @@
}
synchronized (mLock) {
mCallbacks.clear();
+ mImeDispatcher = null;
}
}
@@ -169,4 +175,14 @@
transferCallbacksToDispatcher();
}
}
+
+ @Override
+ public void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ if (mActualDispatcher != null) {
+ mActualDispatcher.setImeOnBackInvokedDispatcher(imeDispatcher);
+ } else {
+ mImeDispatcher = imeDispatcher;
+ }
+ }
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 781859c..edfdbcc 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -55,6 +55,8 @@
.getInt("persist.wm.debug.predictive_back", 1) != 0;
private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
.getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
+ @Nullable
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
@@ -94,6 +96,10 @@
private void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+ return;
+ }
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
@@ -120,6 +126,10 @@
@Override
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+ return;
+ }
if (!mAllCallbacks.containsKey(callback)) {
if (DEBUG) {
Log.i(TAG, "Callback not found. returning...");
@@ -153,6 +163,9 @@
}
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
+ if (mImeDispatcher != null) {
+ mImeDispatcher = null;
+ }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -160,14 +173,18 @@
return;
}
try {
- if (callback == null) {
- mWindowSession.setOnBackInvokedCallbackInfo(mWindow, null);
- } else {
+ OnBackInvokedCallbackInfo callbackInfo = null;
+ if (callback != null) {
int priority = mAllCallbacks.get(callback);
- mWindowSession.setOnBackInvokedCallbackInfo(
- mWindow, new OnBackInvokedCallbackInfo(
- new OnBackInvokedCallbackWrapper(callback), priority));
+ final IOnBackInvokedCallback iCallback =
+ callback instanceof ImeOnBackInvokedDispatcher
+ .ImeOnBackInvokedCallback
+ ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
+ callback).getIOnBackInvokedCallback()
+ : new OnBackInvokedCallbackWrapper(callback);
+ callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
}
+ mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
if (DEBUG && callback == null) {
Log.d(TAG, TextUtils.formatSimple("setTopOnBackInvokedCallback(null) Callers:%s",
Debug.getCallers(5, " ")));
@@ -190,7 +207,7 @@
return null;
}
- private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
+ static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
@@ -270,4 +287,10 @@
return featureFlagEnabled && (appRequestsPredictiveBack || ALWAYS_ENFORCE_PREDICTIVE_BACK);
}
+
+ @Override
+ public void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ mImeDispatcher = imeDispatcher;
+ }
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 7db4243..0976f45 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,8 +21,10 @@
import android.annotation.AnyThread;
import android.annotation.BinderThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
@@ -33,7 +35,6 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindowManager;
@@ -42,6 +43,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
@@ -76,7 +78,7 @@
private boolean mAttachToWindowContainer;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
@@ -188,8 +190,8 @@
@BinderThread
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
- true /* shouldReportConfigChange */));
+ mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
+ newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
}
// TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
@@ -279,12 +281,16 @@
@BinderThread
@Override
public void onWindowTokenRemoved() {
- mHandler.post(() -> {
- final Context context = mContextRef.get();
- if (context != null) {
- context.destroy();
- mContextRef.clear();
- }
- });
+ mHandler.post(PooledLambda.obtainRunnable(
+ WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse());
+ }
+
+ @MainThread
+ private void onWindowTokenRemovedInner() {
+ final Context context = mContextRef.get();
+ if (context != null) {
+ context.destroy();
+ mContextRef.clear();
+ }
}
}
diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java
index 599e6d2..f3a322c 100644
--- a/core/java/com/android/internal/app/AppLocaleStore.java
+++ b/core/java/com/android/internal/app/AppLocaleStore.java
@@ -20,12 +20,15 @@
import android.app.LocaleConfig;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageManager;
import android.os.LocaleList;
import android.util.Log;
-import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Locale;
+import java.util.stream.Collectors;
class AppLocaleStore {
private static final String TAG = AppLocaleStore.class.getSimpleName();
@@ -34,7 +37,8 @@
Context context, String packageName) {
LocaleConfig localeConfig = null;
AppLocaleResult.LocaleStatus localeStatus = LocaleStatus.UNKNOWN_FAILURE;
- ArrayList<Locale> appSupportedLocales = new ArrayList<>();
+ HashSet<Locale> appSupportedLocales = new HashSet<>();
+ HashSet<Locale> assetLocale = getAssetLocales(context, packageName);
try {
localeConfig = new LocaleConfig(context.createPackageContext(packageName, 0));
@@ -45,32 +49,43 @@
if (localeConfig != null) {
if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
LocaleList packageLocaleList = localeConfig.getSupportedLocales();
- if (packageLocaleList.size() > 0) {
+ boolean shouldFilterNotMatchingLocale = !hasInstallerInfo(context, packageName) &&
+ isSystemApp(context, packageName);
+
+ Log.d(TAG, "filterNonMatchingLocale. " +
+ ", shouldFilterNotMatchingLocale: " + shouldFilterNotMatchingLocale +
+ ", assetLocale size: " + assetLocale.size() +
+ ", packageLocaleList size: " + packageLocaleList.size());
+
+ for (int i = 0; i < packageLocaleList.size(); i++) {
+ appSupportedLocales.add(packageLocaleList.get(i));
+ }
+ if (shouldFilterNotMatchingLocale) {
+ appSupportedLocales = filterNotMatchingLocale(appSupportedLocales, assetLocale);
+ }
+
+ if (appSupportedLocales.size() > 0) {
localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
- for (int i = 0; i < packageLocaleList.size(); i++) {
- appSupportedLocales.add(packageLocaleList.get(i));
- }
} else {
localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE_IN_APP;
}
} else if (localeConfig.getStatus() == LocaleConfig.STATUS_NOT_SPECIFIED) {
- String[] languages = getAssetLocales(context, packageName);
- if (languages.length > 0) {
+ if (assetLocale.size() > 0) {
localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
- for (String language : languages) {
- appSupportedLocales.add(Locale.forLanguageTag(language));
- }
+ appSupportedLocales = assetLocale;
} else {
localeStatus = LocaleStatus.ASSET_LOCALE_IS_EMPTY;
}
}
}
- Log.d(TAG, "getAppSupportedLocales(). status: " + localeStatus
+ Log.d(TAG, "getAppSupportedLocales(). package: " + packageName
+ + ", status: " + localeStatus
+ ", appSupportedLocales:" + appSupportedLocales.size());
return new AppLocaleResult(localeStatus, appSupportedLocales);
}
- private static String[] getAssetLocales(Context context, String packageName) {
+ private static HashSet<Locale> getAssetLocales(Context context, String packageName) {
+ HashSet<Locale> result = new HashSet<>();
try {
PackageManager packageManager = context.getPackageManager();
String[] locales = packageManager.getResourcesForApplication(
@@ -78,16 +93,59 @@
.applicationInfo).getAssets().getNonSystemLocales();
if (locales == null) {
Log.i(TAG, "[" + packageName + "] locales are null.");
- return new String[0];
} else if (locales.length <= 0) {
Log.i(TAG, "[" + packageName + "] locales length is 0.");
- return new String[0];
+ } else {
+ for (String language : locales) {
+ result.add(Locale.forLanguageTag(language));
+ }
}
- return locales;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
}
- return new String[0];
+ return result;
+ }
+
+ private static HashSet<Locale> filterNotMatchingLocale(
+ HashSet<Locale> appSupportedLocales, HashSet<Locale> assetLocale) {
+ return appSupportedLocales.stream()
+ .filter(locale -> matchLanguageInSet(locale, assetLocale))
+ .collect(Collectors.toCollection(HashSet::new));
+ }
+
+ private static boolean matchLanguageInSet(Locale locale, HashSet<Locale> localesSet) {
+ if (localesSet.contains(locale)) {
+ return true;
+ }
+ for (Locale l: localesSet) {
+ if(LocaleList.matchesLanguageAndScript(l, locale)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasInstallerInfo(Context context, String packageName) {
+ InstallSourceInfo installSourceInfo;
+ try {
+ installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName);
+ return installSourceInfo != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Installer info not found for: " + packageName);
+ }
+ return false;
+ }
+
+ private static boolean isSystemApp(Context context, String packageName) {
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = context.getPackageManager()
+ .getApplicationInfoAsUser(packageName, /* flags= */ 0, context.getUserId());
+ return applicationInfo.isSystemApp();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Application info not found for: " + packageName);
+ }
+ return false;
}
static class AppLocaleResult {
@@ -100,9 +158,9 @@
}
LocaleStatus mLocaleStatus;
- ArrayList<Locale> mAppSupportedLocales;
+ HashSet<Locale> mAppSupportedLocales;
- public AppLocaleResult(LocaleStatus localeStatus, ArrayList<Locale> appSupportedLocales) {
+ public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) {
this.mLocaleStatus = localeStatus;
this.mAppSupportedLocales = appSupportedLocales;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 3fee914..2ad1b38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -971,7 +971,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_COPY,
"",
- -1);
+ -1,
+ false);
setResult(RESULT_OK);
finish();
@@ -1155,7 +1156,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_NEARBY,
"",
- -1);
+ -1,
+ false);
// Action bar is user-independent, always start as primary
safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
finish();
@@ -1177,7 +1179,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_EDIT,
"",
- -1);
+ -1,
+ false);
// Action bar is user-independent, always start as primary
safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
finish();
@@ -1754,7 +1757,8 @@
target.getComponentName().getPackageName()
+ target.getTitle().toString(),
mMaxHashSaltDays);
- directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
+ SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
+ directTargetAlsoRanked = getRankedPosition(selectableTargetInfo);
if (mCallerChooserTargets != null) {
numCallerProvided = mCallerChooserTargets.length;
@@ -1762,7 +1766,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_SERVICE,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ selectableTargetInfo.isPinned()
);
break;
case ChooserListAdapter.TARGET_CALLER:
@@ -1773,7 +1778,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_APP,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ targetInfo.isPinned()
);
break;
case ChooserListAdapter.TARGET_STANDARD_AZ:
@@ -1784,7 +1790,8 @@
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_STANDARD,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ false
);
break;
}
@@ -1854,10 +1861,10 @@
try {
final Intent intent = getTargetIntent();
String dataString = intent.getDataString();
- if (!TextUtils.isEmpty(dataString)) {
- return new IntentFilter(intent.getAction(), dataString);
- }
if (intent.getType() == null) {
+ if (!TextUtils.isEmpty(dataString)) {
+ return new IntentFilter(intent.getAction(), dataString);
+ }
Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
return null;
}
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
index 3217307..bb7d50a 100644
--- a/core/java/com/android/internal/app/ChooserActivityLogger.java
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -34,7 +34,8 @@
int appProvidedApp, boolean isWorkprofile, int previewType, String intent);
/** Logs a UiEventReported event for the system sharesheet when the user selects a target. */
- void logShareTargetSelected(int targetType, String packageName, int positionPicked);
+ void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned);
/** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
default void logSharesheetTriggered() {
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
index 48bdba3..e3cc4f1 100644
--- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -51,12 +51,14 @@
}
@Override
- public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned) {
FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
/* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
/* package_name = 2 */ packageName,
/* instance_id = 3 */ getInstanceId().getId(),
- /* position_picked = 4 */ positionPicked);
+ /* position_picked = 4 */ positionPicked,
+ /* is_pinned = 5 */ isPinned);
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index f6445849..8dd8272 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -144,7 +144,9 @@
}
}
if (ai == null) {
- ri = packageManager.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
+ // Because of AIDL bug, resolveActivity can't accept subclasses of Intent.
+ final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii);
+ ri = packageManager.resolveActivity(rii, PackageManager.MATCH_DEFAULT_ONLY);
ai = ri != null ? ri.activityInfo : null;
}
if (ai == null) {
diff --git a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
index 4f1f380..3e1b5f0 100644
--- a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
@@ -269,7 +269,7 @@
protected CharSequence getItemLabel(DisplayResolveInfo dri) {
final PackageManager pm = getContext().getPackageManager();
return getPinLabel(isPinned(dri),
- isShortcutTarget() ? "" : dri.getResolveInfo().loadLabel(pm));
+ isShortcutTarget() ? mShortcutTitle : dri.getResolveInfo().loadLabel(pm));
}
@Nullable
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index a06ba9b..2247e2f 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -29,13 +29,13 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.MenuItem.OnActionExpandListener;
import android.view.View;
import android.widget.ListView;
import android.widget.SearchView;
import com.android.internal.R;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
@@ -64,6 +64,7 @@
private int mTopDistance = 0;
private String mAppPackageName;
private CharSequence mTitle = null;
+ private OnActionExpandListener mOnActionExpandListener;
/**
* Other classes can register to be notified when a locale was selected.
@@ -80,8 +81,10 @@
private static LocalePickerWithRegion createCountryPicker(Context context,
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
- boolean translatedOnly, String appPackageName) {
+ boolean translatedOnly, String appPackageName,
+ OnActionExpandListener onActionExpandListener) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+ localePicker.setOnActionExpandListener(onActionExpandListener);
boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
translatedOnly, appPackageName);
return shouldShowTheList ? localePicker : null;
@@ -95,8 +98,10 @@
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
- LocaleSelectedListener listener, boolean translatedOnly, String appPackageName) {
+ LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
+ OnActionExpandListener onActionExpandListener) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+ localePicker.setOnActionExpandListener(onActionExpandListener);
localePicker.setListener(
context, listener, /* parent */ null, translatedOnly, appPackageName);
return localePicker;
@@ -198,13 +203,20 @@
}
private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp(
- boolean shouldShowList, ArrayList<Locale> supportedLocales) {
+ boolean shouldShowList, HashSet<Locale> supportedLocales) {
Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
- if (shouldShowList) {
- for(LocaleStore.LocaleInfo li: mLocaleList) {
+ if (!shouldShowList) {
+ return filteredList;
+ }
+
+ for(LocaleStore.LocaleInfo li: mLocaleList) {
+ if (supportedLocales.contains(li.getLocale())) {
+ filteredList.add(li);
+ } else {
for(Locale l: supportedLocales) {
if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
filteredList.add(li);
+ break;
}
}
}
@@ -310,7 +322,7 @@
} else {
LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
getContext(), mListener, locale, mTranslatedOnly /* translate only */,
- mAppPackageName);
+ mAppPackageName, mOnActionExpandListener);
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
@@ -328,8 +340,11 @@
inflater.inflate(R.menu.language_selection_list, menu);
final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
- mSearchView = (SearchView) searchMenuItem.getActionView();
+ if (!mAppPackageName.isEmpty() && mOnActionExpandListener != null) {
+ searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
+ }
+ mSearchView = (SearchView) searchMenuItem.getActionView();
mSearchView.setQueryHint(getText(R.string.search_language_hint));
mSearchView.setOnQueryTextListener(this);
@@ -363,4 +378,11 @@
}
return false;
}
+
+ /**
+ * Sets OnActionExpandListener to LocalePickerWithRegion to dectect action of search bar.
+ */
+ public void setOnActionExpandListener(OnActionExpandListener onActionExpandListener) {
+ mOnActionExpandListener = onActionExpandListener;
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index bd5a73d..a23f841 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -119,6 +119,11 @@
@UnsupportedAppUsage
public ResolverActivity() {
+ mIsIntentPicker = getClass().equals(ResolverActivity.class);
+ }
+
+ protected ResolverActivity(boolean isIntentPicker) {
+ mIsIntentPicker = isIntentPicker;
}
private boolean mSafeForwardingMode;
@@ -135,6 +140,8 @@
private String mReferrerPackage;
private CharSequence mTitle;
private int mDefaultTitleResId;
+ // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity.
+ private final boolean mIsIntentPicker;
// Whether or not this activity supports choosing a default handler for the intent.
@VisibleForTesting
@@ -445,10 +452,6 @@
+ (categories != null ? Arrays.toString(categories.toArray()) : ""));
}
- private boolean isIntentPicker() {
- return getClass().equals(ResolverActivity.class);
- }
-
protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -637,6 +640,11 @@
resetButtonBar();
+ if (shouldUseMiniResolver()) {
+ View buttonContainer = findViewById(R.id.button_bar_container);
+ buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom);
+ }
+
// Need extra padding so the list can fully scroll up
if (shouldAddFooterView()) {
applyFooterView(mSystemWindowInsets.bottom);
@@ -649,7 +657,8 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- if (isIntentPicker() && shouldShowTabs() && !useLayoutWithDefault()) {
+ if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
+ && !shouldUseMiniResolver()) {
updateIntentPickerPaddings();
}
@@ -1084,7 +1093,7 @@
if (isAutolaunching()) {
return;
}
- if (isIntentPicker()) {
+ if (mIsIntentPicker) {
((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
.setUseLayoutWithDefault(useLayoutWithDefault());
}
@@ -1108,7 +1117,7 @@
protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
final ItemClickListener listener = new ItemClickListener();
setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
- if (shouldShowTabs() && isIntentPicker()) {
+ if (shouldShowTabs() && mIsIntentPicker) {
final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel);
if (rdl != null) {
rdl.setMaxCollapsedHeight(getResources()
@@ -1448,6 +1457,12 @@
return postRebuildList(rebuildCompleted);
}
+ /**
+ * Mini resolver is shown when the user is choosing between browser[s] in this profile and a
+ * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon
+ * and asks the user if they'd like to open that cross-profile app or use the in-profile
+ * browser.
+ */
private void configureMiniResolverContent() {
mLayoutId = R.layout.miniresolver;
setContentView(mLayoutId);
@@ -1484,7 +1499,16 @@
});
}
+ /**
+ * Mini resolver should be used when all of the following are true:
+ * 1. This is the intent picker (ResolverActivity).
+ * 2. This profile only has web browser matches.
+ * 3. The other profile has a single non-browser match.
+ */
private boolean shouldUseMiniResolver() {
+ if (!mIsIntentPicker) {
+ return false;
+ }
if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
|| mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
return false;
@@ -1790,7 +1814,7 @@
void onHorizontalSwipeStateChanged(int state) {}
private void maybeHideDivider() {
- if (!isIntentPicker()) {
+ if (!mIsIntentPicker) {
return;
}
final View divider = findViewById(R.id.divider);
@@ -1807,7 +1831,7 @@
protected void onProfileTabSelected() { }
private void resetCheckedItem() {
- if (!isIntentPicker()) {
+ if (!mIsIntentPicker) {
return;
}
mLastSelected = ListView.INVALID_POSITION;
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index c5b21ac..e7f80a7 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -589,7 +589,7 @@
MetricsLogger metricsLogger = new MetricsLogger();
LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
log.setComponentName(mRankerServiceName);
- log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed);
+ log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed ? 1 : 0);
log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
metricsLogger.write(log);
}
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 957a636..e56d92b 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -91,7 +91,12 @@
} else {
builder.setPositiveButton(R.string.ok, null);
}
- builder.show();
+ final AlertDialog dialog = builder.create();
+ dialog.create();
+ // Prevents screen overlay attack.
+ getWindow().setHideOverlayWindows(true);
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ dialog.show();
}
private String getDialogTitle() {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e466c88..2e4860a 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -71,6 +71,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
@@ -194,6 +195,7 @@
public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
public static final int CUJ_SETTINGS_SLIDER = 53;
+ public static final int CUJ_TAKE_SCREENSHOT = 54;
private static final int NO_STATSD_LOGGING = -1;
@@ -256,6 +258,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT,
};
private static volatile InteractionJankMonitor sInstance;
@@ -330,6 +333,7 @@
CUJ_LOCKSCREEN_LAUNCH_CAMERA,
CUJ_SPLIT_SCREEN_RESIZE,
CUJ_SETTINGS_SLIDER,
+ CUJ_TAKE_SCREENSHOT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -756,6 +760,8 @@
return "CUJ_SPLIT_SCREEN_RESIZE";
case CUJ_SETTINGS_SLIDER:
return "SETTINGS_SLIDER";
+ case CUJ_TAKE_SCREENSHOT:
+ return "TAKE_SCREENSHOT";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index 983e0fe..ffbf646 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -69,7 +69,8 @@
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ 0,
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
}
}
@@ -82,7 +83,8 @@
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ instance.getId(),
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
} else if ((eventID > 0)) {
logWithPosition(event, uid, packageName, position);
}
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 3eb9804..5adaf4f 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -28,18 +28,15 @@
public final class NotificationAccessConfirmationActivityContract {
public static final String EXTRA_USER_ID = "user_id";
public static final String EXTRA_COMPONENT_NAME = "component_name";
- public static final String EXTRA_PACKAGE_TITLE = "package_title";
/**
* Creates a launcher intent for NotificationAccessConfirmationActivity.
*/
- public static Intent launcherIntent(Context context, int userId, ComponentName component,
- String packageTitle) {
+ public static Intent launcherIntent(Context context, int userId, ComponentName component) {
return new Intent()
.setComponent(ComponentName.unflattenFromString(context.getString(
R.string.config_notificationAccessConfirmationActivity)))
.putExtra(EXTRA_USER_ID, userId)
- .putExtra(EXTRA_COMPONENT_NAME, component)
- .putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
+ .putExtra(EXTRA_COMPONENT_NAME, component);
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 3f87de2..b03a8cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 207;
+ static final int VERSION = 208;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -3981,8 +3981,7 @@
if (idxObj != null) {
idx = idxObj;
if ((idx & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- idx &= ~TAG_FIRST_OCCURRENCE_FLAG;
- mHistoryTagPool.put(tag, idx);
+ mHistoryTagPool.put(tag, idx & ~TAG_FIRST_OCCURRENCE_FLAG);
}
return idx;
} else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1db4bbb..ea5797d 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1262,7 +1262,7 @@
}
}
- if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
+ if (forceConsumingNavBar && !hideNavigation && !mDrawLegacyNavigationBarBackgroundHandled) {
mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
} else {
mBackgroundInsets = Insets.NONE;
diff --git a/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java
index fc064ea..4173abf 100644
--- a/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java
+++ b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java
@@ -16,13 +16,18 @@
package com.android.internal.policy;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import java.util.Collection;
+
/**
* A ContentObserver for listening {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE} setting key.
*
@@ -59,7 +64,11 @@
}
@Override
- public void onChange(boolean selfChange) {
+ public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags, int userId) {
+ if (userId != ActivityManager.getCurrentUser()) {
+ return;
+ }
+
if (mOnChangeRunnable != null) {
mOnChangeRunnable.run();
}
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
index 397b2c0..62dea9d 100644
--- a/core/java/com/android/internal/util/ImageUtils.java
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -137,6 +137,18 @@
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) {
+ return buildScaledBitmap(drawable, maxWidth, maxHeight, false);
+ }
+
+ /**
+ * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
+ *
+ * @param allowUpscaling if true, the drawable will not only be scaled down, but also scaled up
+ * to fit within the maximum size given. This is useful for converting
+ * vectorized icons which usually have a very small intrinsic size.
+ */
+ public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
+ int maxHeight, boolean allowUpscaling) {
if (drawable == null) {
return null;
}
@@ -155,7 +167,9 @@
// a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight);
- ratio = Math.min(1.0f, ratio);
+ if (!allowUpscaling) {
+ ratio = Math.min(1.0f, ratio);
+ }
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 40d89db..4e2526a 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -23,6 +23,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
@@ -47,7 +48,8 @@
void unbindInput();
void startInput(in IBinder startInputToken, in IInputContext inputContext,
- in EditorInfo attribute, boolean restarting, int navigationBarFlags);
+ in EditorInfo attribute, boolean restarting, int navigationBarFlags,
+ in ImeOnBackInvokedDispatcher imeDispatcher);
void onNavButtonFlagsChanged(int navButtonFlags);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index d7bb2cb..3157760 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -20,6 +20,7 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
@@ -57,7 +58,7 @@
/* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
int windowFlags, in EditorInfo attribute, in IInputContext inputContext,
in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion);
+ int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
diff --git a/core/java/com/android/internal/widget/PasswordValidationError.java b/core/java/com/android/internal/widget/PasswordValidationError.java
index 41b234e..f678b13 100644
--- a/core/java/com/android/internal/widget/PasswordValidationError.java
+++ b/core/java/com/android/internal/widget/PasswordValidationError.java
@@ -24,16 +24,17 @@
public static final int WEAK_CREDENTIAL_TYPE = 1;
public static final int CONTAINS_INVALID_CHARACTERS = 2;
public static final int TOO_SHORT = 3;
- public static final int TOO_LONG = 4;
- public static final int CONTAINS_SEQUENCE = 5;
- public static final int NOT_ENOUGH_LETTERS = 6;
- public static final int NOT_ENOUGH_UPPER_CASE = 7;
- public static final int NOT_ENOUGH_LOWER_CASE = 8;
- public static final int NOT_ENOUGH_DIGITS = 9;
- public static final int NOT_ENOUGH_SYMBOLS = 10;
- public static final int NOT_ENOUGH_NON_LETTER = 11;
- public static final int NOT_ENOUGH_NON_DIGITS = 12;
- public static final int RECENTLY_USED = 13;
+ public static final int TOO_SHORT_WHEN_ALL_NUMERIC = 4;
+ public static final int TOO_LONG = 5;
+ public static final int CONTAINS_SEQUENCE = 6;
+ public static final int NOT_ENOUGH_LETTERS = 7;
+ public static final int NOT_ENOUGH_UPPER_CASE = 8;
+ public static final int NOT_ENOUGH_LOWER_CASE = 9;
+ public static final int NOT_ENOUGH_DIGITS = 10;
+ public static final int NOT_ENOUGH_SYMBOLS = 11;
+ public static final int NOT_ENOUGH_NON_LETTER = 12;
+ public static final int NOT_ENOUGH_NON_DIGITS = 13;
+ public static final int RECENTLY_USED = 14;
// WARNING: if you add a new error, make sure it is presented to the user correctly in Settings.
public final int errorCode;
@@ -61,6 +62,7 @@
case WEAK_CREDENTIAL_TYPE: return "Weak credential type";
case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character";
case TOO_SHORT: return "Password too short";
+ case TOO_SHORT_WHEN_ALL_NUMERIC: return "Password too short";
case TOO_LONG: return "Password too long";
case CONTAINS_SEQUENCE: return "Sequence too long";
case NOT_ENOUGH_LETTERS: return "Too few letters";
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index f7af67b..c484525 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,7 +21,6 @@
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
-import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
@@ -93,10 +92,7 @@
* enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
*/
static FloatingToolbarPopup createInstance(Context context, View parent) {
- boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
- return enabled
- ? new RemoteFloatingToolbarPopup(context, parent)
- : new LocalFloatingToolbarPopup(context, parent);
+ return new LocalFloatingToolbarPopup(context, parent);
}
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index b6fbe20..f24c666 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1264,6 +1264,12 @@
size_t numPositionMasks = 0;
size_t numIndexMasks = 0;
+ int audioFormat = audioFormatFromNative(nAudioProfile->format);
+ if (audioFormat == ENCODING_INVALID) {
+ ALOGW("Unknown native audio format for JAVA API: %u", nAudioProfile->format);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
// count up how many masks are positional and indexed
for (size_t index = 0; index < nAudioProfile->num_channel_masks; index++) {
const audio_channel_mask_t mask = nAudioProfile->channel_masks[index];
@@ -1306,10 +1312,9 @@
ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
}
- *jAudioProfile =
- env->NewObject(gAudioProfileClass, gAudioProfileCstor,
- audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
- jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
+ *jAudioProfile = env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
+ jSamplingRates.get(), jChannelMasks.get(),
+ jChannelIndexMasks.get(), encapsulationType);
if (*jAudioProfile == nullptr) {
return AUDIO_JAVA_ERROR;
@@ -1368,6 +1373,10 @@
jobject jAudioProfile = nullptr;
jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
useInMask);
+ if (jStatus == AUDIO_JAVA_BAD_VALUE) {
+ // skipping Java layer unsupported audio formats
+ continue;
+ }
if (jStatus != NO_ERROR) {
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
@@ -2406,8 +2415,13 @@
goto exit;
}
for (size_t i = 0; i < numSurroundFormats; i++) {
- jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor,
- audioFormatFromNative(surroundFormats[i]));
+ int audioFormat = audioFormatFromNative(surroundFormats[i]);
+ if (audioFormat == ENCODING_INVALID) {
+ // skipping Java layer unsupported audio formats
+ ALOGW("Unknown surround native audio format for JAVA API: %u", surroundFormats[i]);
+ continue;
+ }
+ jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor, audioFormat);
jobject enabled = env->NewObject(gBooleanClass, gBooleanCstor, surroundFormatsEnabled[i]);
env->CallObjectMethod(jSurroundFormats, gMapPut, surroundFormat, enabled);
env->DeleteLocalRef(surroundFormat);
@@ -2453,8 +2467,13 @@
goto exit;
}
for (size_t i = 0; i < numSurroundFormats; i++) {
- jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor,
- audioFormatFromNative(surroundFormats[i]));
+ int audioFormat = audioFormatFromNative(surroundFormats[i]);
+ if (audioFormat == ENCODING_INVALID) {
+ // skipping Java layer unsupported audio formats
+ ALOGW("Unknown surround native audio format for JAVA API: %u", surroundFormats[i]);
+ continue;
+ }
+ jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor, audioFormat);
env->CallObjectMethod(jSurroundFormats, gArrayListMethods.add, surroundFormat);
env->DeleteLocalRef(surroundFormat);
}
@@ -2919,6 +2938,10 @@
for (const auto &audioProfile : audioProfiles) {
jobject jAudioProfile;
jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false);
+ if (jStatus == AUDIO_JAVA_BAD_VALUE) {
+ // skipping Java layer unsupported audio formats
+ continue;
+ }
if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index c769da5..4044c57 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1921,6 +1921,7 @@
FocusRequest request;
request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
request.displayId = displayId;
+ request.windowName = "<null>";
transaction->setFocusedWindow(request);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0f328b0..f20b824 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6441,11 +6441,11 @@
<!-- @SystemApi Must be required by a safety source to send an update using the
{@link android.safetycenter.SafetyCenterManager}.
- <p>Protection level: signature|privileged
+ <p>Protection level: internal|privileged
@hide
-->
<permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="internal|privileged" />
<!-- @SystemApi Allows an application to launch device manager setup screens.
<p>Not for use by third-party applications.
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index 252f59e..0b6a201 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -50,6 +50,7 @@
android:visibility="gone" />
</LinearLayout>
+ <!-- For Authentication. -->
<LinearLayout
android:id="@+id/autofill_dialog_container"
android:layout_width="fill_parent"
@@ -58,7 +59,7 @@
android:paddingStart="@dimen/autofill_save_inner_padding"
android:paddingEnd="@dimen/autofill_save_inner_padding"
android:visibility="gone"
- style="@style/AutofillDatasetPicker" />
+ android:background="@drawable/autofill_dataset_picker_background"/>
<ListView
android:id="@+id/autofill_dialog_list"
@@ -68,8 +69,8 @@
android:drawSelectorOnTop="true"
android:clickable="true"
android:divider="?android:attr/listDivider"
- android:visibility="gone"
- style="@style/AutofillDatasetPicker" />
+ android:background="@drawable/autofill_dataset_picker_background"
+ android:visibility="gone"/>
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 6242a3d..abe40f3 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1680,7 +1680,7 @@
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"FRA"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Vil du give <xliff:g id="SERVICE">%1$s</xliff:g> fuld kontrol over din enhed?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"Fuld kontrol er velegnet til apps, der hjælper dig med hjælpefunktioner, men ikke de fleste apps."</string>
- <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og styre skærm"</string>
+ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og styre skærmen"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan læse alt indhold på skærmen og vise indhold oven på andre apps."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Se og udføre handlinger"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore dine interaktioner med en app eller en hardwaresensor og interagere med apps på dine vegne."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 18b3bf6..1c23424 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1387,7 +1387,7 @@
<string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"Bistaratu beste aplikazioen gainean"</string>
<string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> aplikazioen gainean agertzea"</string>
<string name="alert_windows_notification_title" msgid="6331662751095228536">"Besteen gainean agertzen da <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="alert_windows_notification_message" msgid="6538171456970725333">"Ez baduzu nahi <xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string>
+ <string name="alert_windows_notification_message" msgid="6538171456970725333">"<xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea nahi ez baduzu, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string>
<string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Desaktibatu"</string>
<string name="ext_media_checking_notification_title" msgid="8299199995416510094">"<xliff:g id="NAME">%s</xliff:g> egiaztatzen…"</string>
<string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Edukia berrikusten"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1e0851b..4a904cf 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1312,7 +1312,7 @@
<string name="select_character" msgid="3352797107930786979">"درج نویسه"</string>
<string name="sms_control_title" msgid="4748684259903148341">"درحال ارسال پیامکها"</string>
<string name="sms_control_message" msgid="6574313876316388239">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> درحال ارسال تعداد زیادی پیامک است. آیا اجازه میدهید این برنامه همچنان پیامک ارسال کند؟"</string>
- <string name="sms_control_yes" msgid="4858845109269524622">"مجاز است"</string>
+ <string name="sms_control_yes" msgid="4858845109269524622">"اجازه دادن"</string>
<string name="sms_control_no" msgid="4845717880040355570">"مجاز نبودن"</string>
<string name="sms_short_code_confirm_message" msgid="1385416688897538724">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> مایل است پیامی به <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b> ارسال کند."</string>
<string name="sms_short_code_details" msgid="2723725738333388351">"این مورد "<b>"شاید هزینهای"</b>" را به حساب دستگاه همراهتان بگذارد."</string>
@@ -1684,7 +1684,7 @@
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"میتواند همه محتوای صفحه را بخواند و آن را روی بقیه برنامهها نمایش دهد."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"مشاهده و انجام کنشها"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"این عملکرد میتواند با برنامه یا حسگری سختافزاری تعاملاتتان را ردیابی کند و ازطرف شما با برنامهها تعامل داشته باشد."</string>
- <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"مجاز"</string>
+ <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازه دادن"</string>
<string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مجاز نبودن"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"برای استفاده از ویژگی، روی آن ضربه بزنید:"</string>
<string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"انتخاب ویژگیهای موردنظر برای استفاده با دکمه دسترسپذیری"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 9a65373..fe5c92c 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -2261,7 +2261,7 @@
<string name="window_magnification_prompt_title" msgid="2876703640772778215">"Novas opcións de configuración de ampliación"</string>
<string name="window_magnification_prompt_content" msgid="8159173903032344891">"Xa podes ampliar parte da pantalla"</string>
<string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activar en Configuración"</string>
- <string name="dismiss_action" msgid="1728820550388704784">"Ignorar"</string>
+ <string name="dismiss_action" msgid="1728820550388704784">"Pechar"</string>
<string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Desbloquea o micrófono do dispositivo"</string>
<string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Desbloquea a cámara do dispositivo"</string>
<string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Para <b><xliff:g id="APP">%s</xliff:g></b> e todas as aplicacións e servizos"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 88b692e..6f214a8 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -245,10 +245,10 @@
<string name="global_action_power_options" msgid="1185286119330160073">"Սնուցման կոճակ"</string>
<string name="global_action_restart" msgid="4678451019561687074">"Վերագործարկել"</string>
<string name="global_action_emergency" msgid="1387617624177105088">"Շտապ կանչ"</string>
- <string name="global_action_bug_report" msgid="5127867163044170003">"Վրիպակի զեկույց"</string>
+ <string name="global_action_bug_report" msgid="5127867163044170003">"Հաղորդում վրիպակի մասին"</string>
<string name="global_action_logout" msgid="6093581310002476511">"Ավարտել աշխատաշրջանը"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Սքրինշոթ"</string>
- <string name="bugreport_title" msgid="8549990811777373050">"Հաշվետվություն վրիպակի մասին"</string>
+ <string name="bugreport_title" msgid="8549990811777373050">"Հաղորդում վրիպակի մասին"</string>
<string name="bugreport_message" msgid="5212529146119624326">"Սա տեղեկություններ կհավաքագրի ձեր սարքի առկա կարգավիճակի մասին և կուղարկի այն էլեկտրոնային նամակով: Որոշակի ժամանակ կպահանջվի վրիպակի մասին զեկուցելու պահից սկսած մինչ ուղարկելը: Խնդրում ենք փոքր-ինչ համբերատար լինել:"</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Ինտերակտիվ զեկույց"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Հիմնականում օգտագործեք այս տարբերակը: Այն ձեզ թույլ է տալիս հետևել զեկույցի ստեղծման գործընթացին, խնդրի մասին լրացուցիչ տեղեկություններ մուտքագրել և սքրինշոթներ ստեղծել: Կարող է բաց թողնել քիչ օգտագործվող որոշ բաժիններ, որոնց ստեղծումը երկար է տևում:"</string>
@@ -1372,7 +1372,7 @@
<string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"USB միացքը կարող է օգտագործվել"</string>
<string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Հեռախոսում ջուր կամ աղտ չի հայտնաբերվել:"</string>
<string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Վրիպակի զեկույցի ստեղծում…"</string>
- <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Տրամադրե՞լ վրիպակի զեկույցը:"</string>
+ <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Կիսվե՞լ վրիպակի մասին հաղորդմամբ"</string>
<string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Վրիպակի զեկույցի տրամադրում…"</string>
<string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Այս սարքի անսարքությունների վերացման նպատակով ձեր ադմինիստրատորին անհրաժեշտ է վրիպակի հաշվետվություն: Կարող են տրամադրվել տեղեկություններ հավելվածների մասին և այլ տվյալներ։"</string>
<string name="share_remote_bugreport_action" msgid="7630880678785123682">"ՏՐԱՄԱԴՐԵԼ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 6d26efd..0a5b9b6 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1696,7 +1696,7 @@
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"단축키 사용"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"색상 반전"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"색상 보정"</string>
- <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 사용 모드"</string>
+ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 모드"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"더 어둡게"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"볼륨 키를 길게 눌렀습니다. <xliff:g id="SERVICE_NAME">%1$s</xliff:g>이(가) 사용 설정되었습니다."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"볼륨 키를 길게 눌렀습니다. <xliff:g id="SERVICE_NAME">%1$s</xliff:g>이(가) 사용 중지되었습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index f7c4ca3..d86371b 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -221,7 +221,7 @@
<string name="silent_mode_silent" msgid="5079789070221150912">"Коңгуроо өчүк"</string>
<string name="silent_mode_vibrate" msgid="8821830448369552678">"Чалганда титирөө"</string>
<string name="silent_mode_ring" msgid="6039011004781526678">"Коңгуроо жандырылган"</string>
- <string name="reboot_to_update_title" msgid="2125818841916373708">"Android тутум жаңыртуусу"</string>
+ <string name="reboot_to_update_title" msgid="2125818841916373708">"Android системасын жаңыртуу"</string>
<string name="reboot_to_update_prepare" msgid="6978842143587422365">"Жаңыртууга даярдалууда…"</string>
<string name="reboot_to_update_package" msgid="4644104795527534811">"Жаңыртуу топтому иштелүүдө…"</string>
<string name="reboot_to_update_reboot" msgid="4474726009984452312">"Өчүрүлүп күйгүзүлүүдө…"</string>
@@ -1670,10 +1670,10 @@
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string>
- <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
+ <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string>
- <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
+ <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Жок"</string>
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"КҮЙҮК"</string>
@@ -1681,7 +1681,7 @@
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматына түзмөгүңүздү толугу менен көзөмөлдөөгө уруксат бересизби?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"Толук көзөмөл атайын мүмкүнчүлүктөрдү пайдаланууга керек, бирок калган көпчүлүк колдонмолорго кереги жок."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Экранды көрүп, көзөмөлдөө"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Кызмат экрандагы нерселерди окуп, материалды башка колдонмолордун үстүнөн көрсөтөт."</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Кызмат экрандагы нерселерди окуп, аларды башка колдонмолордун үстүнөн көрсөтөт."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Аракеттерди көрүп, аткаруу"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Кызмат колдонмодо жасаган аракеттериңизге же түзмөктүн сенсорлоруна көз салып, сиздин атыңыздан буйруктарды берет."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Уруксат берүү"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 7dbf02a..bcb06b9 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1681,7 +1681,7 @@
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> എന്നതിന് നിങ്ങളുടെ ഉപകരണത്തിന്മേൽ പൂർണ്ണ നിയന്ത്രണം അനുവദിക്കണോ?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"ഉപയോഗസഹായി ആവശ്യങ്ങൾക്കായി നിങ്ങളെ സഹായിക്കുന്ന ആപ്പുകൾക്ക് പൂർണ്ണ നിയന്ത്രണം അനുയോജ്യമാണെങ്കിലും മിക്ക ആപ്പുകൾക്കും അനുയോജ്യമല്ല."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"സ്ക്രീൻ കാണുക, നിയന്ത്രിക്കുക"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ഇതിന് സ്ക്രീനിലെ എല്ലാ ഉള്ളടക്കവും വായിക്കാനും മറ്റ് ആപ്പുകളിൽ ഉള്ളടക്കം പ്രദർശിപ്പിക്കാനുമാവും."</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ഇതിന് സ്ക്രീനിലെ എല്ലാ ഉള്ളടക്കവും വായിക്കാനും മറ്റ് ആപ്പുകൾക്ക് മുകളിൽ ഉള്ളടക്കം പ്രദർശിപ്പിക്കാനുമാകും."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"കാണുക, പ്രവർത്തനങ്ങൾ നിർവഹിക്കുക"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ഇതിന് ഒരു ആപ്പുമായോ ഹാർഡ്വെയർ സെൻസറുമായോ ഉള്ള നിങ്ങളുടെ ആശയവിനിമയങ്ങൾ ട്രാക്ക് ചെയ്യാനും നിങ്ങളുടെ പേരിൽ ആശയവിനിമയം നടത്താനും കഴിയും."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"അനുവദിക്കൂ"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 8325473..e4b034d 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -103,7 +103,7 @@
<string name="serviceClassVoice" msgid="2065556932043454987">"व्हॉइस"</string>
<string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string>
<string name="serviceClassFAX" msgid="2561653371698904118">"फॅक्स"</string>
- <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string>
+ <string name="serviceClassSMS" msgid="1547664561704509004">"एसएमएस"</string>
<string name="serviceClassDataAsync" msgid="2029856900898545984">"असंकालिक"</string>
<string name="serviceClassDataSync" msgid="7895071363569133704">"सिंक करा"</string>
<string name="serviceClassPacket" msgid="1430642951399303804">"पॅकेट"</string>
@@ -303,7 +303,7 @@
<string name="permgroupdesc_location" msgid="1995955142118450685">"या डिव्हाइसच्या स्थानावर प्रवेश"</string>
<string name="permgrouplab_calendar" msgid="6426860926123033230">"कॅलेंडर"</string>
<string name="permgroupdesc_calendar" msgid="6762751063361489379">"आपल्या कॅलेंडरवर प्रवेश"</string>
- <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
+ <string name="permgrouplab_sms" msgid="795737735126084874">"एसएमएस"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS मेसेज पाठवणे आणि पाहणे हे"</string>
<string name="permgrouplab_storage" msgid="5570124978732352858">"फाइल आणि दस्तऐवज"</string>
<string name="permgroupdesc_storage" msgid="8352226729501080525">"तुमच्या डिव्हाइसवर फाइल आणि दस्तऐवज अॅक्सेस करा"</string>
@@ -1683,7 +1683,7 @@
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"स्क्रीन पहा आणि नियंत्रित करा"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ते स्क्रीनवरील सर्व आशय वाचू शकते आणि इतर ॲप्सवर आशय प्रदर्शित करू शकते."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"पहा आणि क्रिया करा"</string>
- <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"तुम्ही ॲप किंवा हार्डवेअर सेन्सर कसे वापरता याचा हे मागोवा घेऊ शकते आणि इतर ॲप्ससोबत तुमच्या वतीने काम करू शकते."</string>
+ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"हे तुम्ही ॲप किंवा हार्डवेअर सेन्सर कसे वापरता ते ट्रॅक करू शकते आणि इतर ॲप्ससोबत तुमच्या वतीने संवाद साधू शकते."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमती द्या"</string>
<string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नकार द्या"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"वैशिष्ट्य वापरणे सुरू करण्यासाठी त्यावर टॅप करा:"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fc1495d..e1a4d6c 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1681,7 +1681,7 @@
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> ကို သင့်စက်အား အပြည့်အဝထိန်းချုပ်ခွင့် ပေးလိုပါသလား။"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"အများသုံးစွဲနိုင်မှု လိုအပ်ချက်များအတွက် အထောက်အကူပြုသည့် အက်ပ်များအား အပြည့်အဝ ထိန်းချုပ်ခွင့်ပေးခြင်းသည် သင့်လျော်သော်လည်း အက်ပ်အများစုအတွက် မသင့်လျော်ပါ။"</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"ဖန်သားပြင်ကို ကြည့်ရှုထိန်းချုပ်ခြင်း"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"၎င်းသည် မျက်နှာပြင်ပေါ်ရှိ အကြောင်းအရာများအားလုံးကို ဖတ်နိုင်ပြီး အခြားအက်ပ်များအပေါ်တွင် ထိုအကြောင်းအရာကို ဖော်ပြနိုင်သည်။"</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"၎င်းသည် မျက်နှာပြင်ပေါ်ရှိ အကြောင်းအရာအားလုံးကို ဖတ်နိုင်ပြီး အခြားအက်ပ်များအပေါ်တွင် အကြောင်းအရာကို ဖော်ပြနိုင်သည်။"</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"လုပ်ဆောင်ချက်များကို ကြည့်ရှုဆောင်ရွက်ခြင်း"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"၎င်းသည် အက်ပ်တစ်ခု သို့မဟုတ် အာရုံခံကိရိယာကို အသုံးပြု၍ သင့်ပြန်လှန်တုံ့ပြန်မှုများကို မှတ်သားနိုင်ပြီး သင့်ကိုယ်စား အက်ပ်များနှင့် ပြန်လှန်တုံ့ပြန်နိုင်သည်။"</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ခွင့်ပြုရန်"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5fff691..01fed17 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1679,9 +1679,9 @@
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"AAN"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"UIT"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Toestaan dat <xliff:g id="SERVICE">%1$s</xliff:g> volledige controle over je apparaat heeft?"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"Volledige controle is gepast voor apps die je helpen met toegankelijkheid, maar voor de meeste apps is het ongepast."</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"Volledige controle is gepast voor apps die je helpen met toegankelijkheid, maar niet voor de meeste apps."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Scherm bekijken en bedienen"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"De functie kan alle content op het scherm lezen en content bovenop andere apps weergeven"</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"De functie kan alle content op het scherm lezen en content bovenop andere apps weergeven."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Acties bekijken en uitvoeren"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"De functie kan je interacties met een app of een hardwaresensor bijhouden en namens jou met apps communiceren."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Toestaan"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index a666c81..b43745e 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -351,7 +351,7 @@
<string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Aplikaciji omogoča razširjanje ali strnjevanje vrstice stanja."</string>
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"Prikaz obvestil kot celozaslonskih dejavnosti v zaklenjeni napravi"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Aplikaciji dovoli, da prikaže obvestila kot celozaslonske dejavnosti v zaklenjeni napravi."</string>
- <string name="permlab_install_shortcut" msgid="7451554307502256221">"nameščanje bližnjic"</string>
+ <string name="permlab_install_shortcut" msgid="7451554307502256221">"Nameščanje bližnjic"</string>
<string name="permdesc_install_shortcut" msgid="4476328467240212503">"Aplikaciji omogoča dodajanje bližnjic na začetni zaslon brez posredovanja uporabnika."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"odstranjevanje bližnjic"</string>
<string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Aplikaciji omogoča odstranjevanje bližnjic z začetnega zaslona brez posredovanja uporabnika."</string>
@@ -1699,7 +1699,7 @@
<string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija barv"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"Popravljanje barv"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enoročni način"</string>
- <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjeno"</string>
+ <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjen zaslon"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Tipki za glasnost sta pridržani. Storitev <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je vklopljena."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Tipki za glasnost sta pridržani. Storitev <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je izklopljena."</string>
<string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Za uporabo storitve <xliff:g id="SERVICE_NAME">%1$s</xliff:g> pritisnite obe tipki za glasnost in ju pridržite tri sekunde"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index dcf9fcd..044264b 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1679,7 +1679,7 @@
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"เปิด"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ปิด"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"อนุญาตให้ <xliff:g id="SERVICE">%1$s</xliff:g> ควบคุมอุปกรณ์อย่างเต็มที่ไหม"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มที่เหมาะสำหรับแอปที่ช่วยคุณในเรื่องความต้องการความช่วยเหลือพิเศษแต่ไม่เหมาะสำหรับแอปส่วนใหญ่"</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มที่เหมาะสำหรับแอปเกี่ยวกับความช่วยเหลือพิเศษ แต่ไม่เหมาะสำหรับแอปส่วนใหญ่"</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"ดูและควบคุมหน้าจอ"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"การควบคุมนี้สามารถอ่านเนื้อหาทั้งหมดบนหน้าจอและแสดงเนื้อหาทับแอปอื่นๆ"</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"ดูและดำเนินการ"</string>
@@ -1712,7 +1712,7 @@
<string name="user_switching_message" msgid="1912993630661332336">"กำลังเปลี่ยนเป็น <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"กำลังออกจากระบบ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"เจ้าของ"</string>
- <string name="guest_name" msgid="8502103277839834324">"ผู้มาเยือน"</string>
+ <string name="guest_name" msgid="8502103277839834324">"ผู้ใช้ชั่วคราว"</string>
<string name="error_message_title" msgid="4082495589294631966">"ข้อผิดพลาด"</string>
<string name="error_message_change_not_allowed" msgid="843159705042381454">"ผู้ดูแลระบบไม่อนุญาตให้ทำการเปลี่ยนแปลงนี้"</string>
<string name="app_not_found" msgid="3429506115332341800">"ไม่พบแอปพลิเคชันสำหรับการทำงานนี้"</string>
@@ -1958,7 +1958,7 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"เชื่อมต่อ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> แล้ว"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"แตะเพื่อดูไฟล์"</string>
<string name="pin_target" msgid="8036028973110156895">"ปักหมุด"</string>
- <string name="pin_specific_target" msgid="7824671240625957415">"ตรึง <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"ปักหมุด <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"เลิกปักหมุด"</string>
<string name="unpin_specific_target" msgid="3859828252160908146">"เลิกปักหมุด <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"ข้อมูลแอป"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 5ea95e9..394e90c 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1681,9 +1681,9 @@
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> کو آپ کے آلے کا مکمل کنٹرول حاصل کرنے کی اجازت دیں؟"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"مکمل کنٹرول ان ایپس کے لیے مناسب ہے جو ایکسیسبیلٹی کی ضروریات میں آپ کی مدد کرتی ہیں، لیکن زیادہ تر ایپس کیلئے مناسب نہیں۔"</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"اسکرین کو دیکھیں اور کنٹرول کریں"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"یہ تمام مواد کو اسکرین پر پڑھ اور دیگر ایپس پر مواد کو ڈسپلے کر سکتا ہے۔"</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"یہ اسکرین پر موجود تمام مواد کو پڑھ سکتا ہے اور دیگر ایپس پر مواد کو ڈسپلے کر سکتا ہے۔"</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"کارروائیاں دیکھیں اور انجام دیں"</string>
- <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"یہ آپ کے تعاملات کو ایپ یا ہارڈویئر سینسر کے ذریعے ٹریک کر سکتا ہے، اور آپ کی طرف سے ایپ کے ساتھ تعمل کر سکتا ہے۔"</string>
+ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"یہ کسی ایپ یا ہارڈویئر سینسر کے ساتھ آپ کے تعاملات کو ٹریک کر سکتا ہے، اور آپ کی طرف سے ایپس کے ساتھ تعامل کر سکتا ہے۔"</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازت دیں"</string>
<string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مسترد کریں"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ایک خصوصیت کا استعمال شروع کرنے کیلئے اسے تھپتھپائیں:"</string>
@@ -1697,7 +1697,7 @@
<string name="color_inversion_feature_name" msgid="326050048927789012">"رنگوں کی تقلیب"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"رنگ کی تصحیح"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ایک ہاتھ کی وضع"</string>
- <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی دھندلا"</string>
+ <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی مدھم"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"والیوم کی کلیدوں کو دبائے رکھا گیا۔ <xliff:g id="SERVICE_NAME">%1$s</xliff:g> آن ہے۔"</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"والیوم کی کلیدوں کو دبائے رکھا گیا۔ <xliff:g id="SERVICE_NAME">%1$s</xliff:g> آف ہے۔"</string>
<string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> کا استعمال کرنے کے لیے 3 سیکنڈ تک والیوم کی دونوں کلیدوں کو چھوئیں اور دبائے رکھیں"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 178a129..0db56e3 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -581,7 +581,7 @@
<string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
<string name="biometric_not_recognized" msgid="5106687642694635888">"未能識別"</string>
<string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string>
- <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖形或密碼"</string>
+ <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖案或密碼"</string>
<string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定解鎖憑證"</string>
@@ -776,7 +776,7 @@
<string name="policylab_setGlobalProxy" msgid="215332221188670221">"設定裝置的全域代理伺服器"</string>
<string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"設定政策啟用時所要使用的裝置全域代理伺服器,只有裝置擁有者可以設定全域代理伺服器。"</string>
<string name="policylab_expirePassword" msgid="6015404400532459169">"設定螢幕鎖定密碼期限"</string>
- <string name="policydesc_expirePassword" msgid="9136524319325960675">"變更螢幕鎖定密碼、PIN 或圖形的更改頻率。"</string>
+ <string name="policydesc_expirePassword" msgid="9136524319325960675">"變更螢幕鎖定密碼、PIN 或圖案的更改頻率。"</string>
<string name="policylab_encryptedStorage" msgid="9012936958126670110">"設定儲存裝置加密"</string>
<string name="policydesc_encryptedStorage" msgid="1102516950740375617">"必須為儲存的應用程式資料加密。"</string>
<string name="policylab_disableCamera" msgid="5749486347810162018">"停用相機"</string>
@@ -911,7 +911,7 @@
<string name="lockscreen_screen_locked" msgid="7364905540516041817">"螢幕已鎖定。"</string>
<string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"按選單鍵解鎖或撥打緊急電話。"</string>
<string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"按選單鍵解鎖。"</string>
- <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖形來為螢幕解鎖"</string>
+ <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖案來為螢幕解鎖"</string>
<string name="lockscreen_emergency_call" msgid="7500692654885445299">"緊急電話"</string>
<string name="lockscreen_return_to_call" msgid="3156883574692006382">"返回通話"</string>
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"正確!"</string>
@@ -940,12 +940,12 @@
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"請參閱使用者指南或與客戶服務中心聯絡。"</string>
<string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"SIM 卡處於鎖定狀態。"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"正在解除 SIM 卡鎖定..."</string>
- <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
+ <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"您已輸入錯誤的密碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"您已輸入錯誤的 PIN 碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用 Google 登入資料將 Android TV 裝置解鎖。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用 Google 登入資料將 Android TV 裝置解鎖。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"您嘗試解除這部平板電腦的鎖定已失敗 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,剩餘 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次嘗試機會。如果失敗次數超過嘗試次數限制,平板電腦將恢復原廠設定,所有使用者資料均會遺失。"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"您已 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次無法解鎖 Android TV 裝置。如果再失敗 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次,Android TV 裝置將回復原廠設定,所有使用者資料均會遺失。"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"您嘗試解除這部手機的鎖定已失敗 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,剩餘 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次嘗試機會。如果失敗次數超過嘗試次數限制,手機將恢復原廠設定,所有使用者資料均會遺失。"</string>
@@ -953,9 +953,9 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"您已 <xliff:g id="NUMBER">%d</xliff:g> 次無法解鎖 Android TV 裝置,Android TV 裝置現在將回復原廠設定。"</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"您嘗試解除這部手機的鎖定已失敗 <xliff:g id="NUMBER">%d</xliff:g> 次。手機現在會重設為原廠預設值。"</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
- <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"忘記圖形?"</string>
+ <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"忘記圖案?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"帳戶解鎖"</string>
- <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"圖形嘗試次數過多"</string>
+ <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"圖案嘗試次數過多"</string>
<string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"如要解鎖,請以 Google 帳戶登入。"</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"使用者名稱 (電子郵件)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"密碼"</string>
@@ -966,12 +966,12 @@
<string name="lockscreen_unlock_label" msgid="4648257878373307582">"解除鎖定"</string>
<string name="lockscreen_sound_on_label" msgid="1660281470535492430">"開啟音效"</string>
<string name="lockscreen_sound_off_label" msgid="2331496559245450053">"關閉音效"</string>
- <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"已開始繪畫解鎖圖形"</string>
- <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"已清除解鎖圖形"</string>
+ <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"已開始繪畫解鎖圖案"</string>
+ <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"已清除解鎖圖案"</string>
<string name="lockscreen_access_pattern_cell_added" msgid="6746676335293144163">"已加入一格"</string>
<string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"已加入 <xliff:g id="CELL_INDEX">%1$s</xliff:g> 點"</string>
- <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"已畫出解鎖圖形"</string>
- <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"圖形區域。"</string>
+ <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"已畫出解鎖圖案"</string>
+ <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"圖案區域。"</string>
<string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s。第 %2$d 個小工具,共 %3$d 個。"</string>
<string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"新增小工具。"</string>
<string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"空白"</string>
@@ -987,13 +987,13 @@
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g>小工具已刪除。"</string>
<string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"展開解鎖區域。"</string>
<string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"滑動解鎖。"</string>
- <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"圖形解鎖。"</string>
+ <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"圖案解鎖。"</string>
<string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"面孔解鎖。"</string>
<string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"PIN 解鎖。"</string>
<string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM 卡 PIN 碼解鎖。"</string>
<string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"SIM 卡 PUK 解鎖。"</string>
<string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"密碼解鎖。"</string>
- <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"圖形區域。"</string>
+ <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"圖案區域。"</string>
<string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"滑動區域。"</string>
<string name="password_keyboard_label_symbol_key" msgid="2716255580853511949">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="5294837425652726684">"ABC"</string>
@@ -1627,11 +1627,11 @@
<string name="display_manager_overlay_display_name" msgid="5306088205181005861">"重疊效果 #<xliff:g id="ID">%1$d</xliff:g>"</string>
<string name="display_manager_overlay_display_title" msgid="1480158037150469170">"<xliff:g id="NAME">%1$s</xliff:g>:<xliff:g id="WIDTH">%2$d</xliff:g>x<xliff:g id="HEIGHT">%3$d</xliff:g>,<xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
<string name="display_manager_overlay_display_secure_suffix" msgid="2810034719482834679">"(安全)"</string>
- <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"忘記了圖形"</string>
- <string name="kg_wrong_pattern" msgid="1342812634464179931">"圖形錯誤"</string>
+ <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"忘記了圖案"</string>
+ <string name="kg_wrong_pattern" msgid="1342812634464179931">"圖案錯誤"</string>
<string name="kg_wrong_password" msgid="2384677900494439426">"密碼錯誤"</string>
<string name="kg_wrong_pin" msgid="3680925703673166482">"PIN 錯誤"</string>
- <string name="kg_pattern_instructions" msgid="8366024510502517748">"畫出圖形"</string>
+ <string name="kg_pattern_instructions" msgid="8366024510502517748">"畫出圖案"</string>
<string name="kg_sim_pin_instructions" msgid="6479401489471690359">"輸入 SIM 卡 PIN 碼"</string>
<string name="kg_pin_instructions" msgid="7355933174673539021">"輸入 PIN 碼"</string>
<string name="kg_password_instructions" msgid="7179782578809398050">"輸入密碼"</string>
@@ -1644,7 +1644,7 @@
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK 碼應由 8 位數字組成。"</string>
<string name="kg_invalid_puk" msgid="4809502818518963344">"請重新輸入正確的 PUK 碼。如果嘗試輸入的次數過多,SIM 卡將永久停用。"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN 碼不符"</string>
- <string name="kg_login_too_many_attempts" msgid="699292728290654121">"圖形嘗試次數過多"</string>
+ <string name="kg_login_too_many_attempts" msgid="699292728290654121">"圖案嘗試次數過多"</string>
<string name="kg_login_instructions" msgid="3619844310339066827">"如要解鎖,請以 Google 帳戶登入。"</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"使用者名稱 (電子郵件)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"密碼"</string>
@@ -1654,16 +1654,16 @@
<string name="kg_login_checking_password" msgid="4676010303243317253">"正在檢查帳戶…"</string>
<string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"您已輸入錯誤的 PIN 碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
<string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"您已輸入錯誤的密碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"您嘗試了 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次仍未能成功解開這部上鎖的平板電腦。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,平板電腦將回復原廠設定,所有使用者資料均會失去。"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"您已 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次無法解鎖 Android TV 裝置。如果再失敗 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次,Android TV 裝置將回復原廠設定,所有使用者資料均會遺失。"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"您嘗試了 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次仍未能成功解開這部上鎖的手機。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,手機將回復原廠設定,所有使用者資料均會失去。"</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"您嘗試了 <xliff:g id="NUMBER">%d</xliff:g> 次仍未能成功解開這部上鎖的平板電腦。平板電腦現在將回復原廠設定。"</string>
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"您已 <xliff:g id="NUMBER">%d</xliff:g> 次無法解鎖 Android TV 裝置,Android TV 裝置現在將回復原廠設定。"</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"您嘗試了 <xliff:g id="NUMBER">%d</xliff:g> 次仍未能成功解開這部上鎖的手機。手機現在將回復原廠設定。"</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用電郵帳戶解鎖 Android TV 裝置。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用電郵帳戶解鎖 Android TV 裝置。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"移除"</string>
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string>
@@ -1843,7 +1843,7 @@
<string name="managed_profile_label_badge_2" msgid="5673187309555352550">"第二個工作<xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="6882151970556391957">"第三個工作<xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"取消固定時必須輸入 PIN"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖形"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖案"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"已由您的管理員安裝"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"已由您的管理員更新"</string>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5df3dde..b515abc 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -448,5 +448,6 @@
<color name="accessibility_color_inversion_background">#546E7A</color>
<!-- Color of camera light when camera is in use -->
- <color name="camera_privacy_light">#FFFFFF</color>
+ <color name="camera_privacy_light_day">#FFFFFF</color>
+ <color name="camera_privacy_light_night">#FFFFFF</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 689ff66..eec5869 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5662,6 +5662,9 @@
<!-- Whether or not to enable the lock screen entry point for the QR code scanner. -->
<bool name="config_enableQrCodeScannerOnLockScreen">false</bool>
+ <!-- Default component for QR code scanner -->
+ <string name="config_defaultQrCodeComponent"></string>
+
<!-- Whether Low Power Standby is supported and can be enabled. -->
<bool name="config_lowPowerStandbySupported">false</bool>
@@ -5787,4 +5790,9 @@
<!-- List of the labels of requestable device state config values -->
<string-array name="config_deviceStatesAvailableForAppRequests"/>
+
+ <!-- Interval in milliseconds to average light sensor values for camera light brightness -->
+ <integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer>
+ <!-- Light sensor's lux value to use as the threshold between using day or night brightness -->
+ <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index d9ac516..682ce46 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -35,7 +35,7 @@
rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
If this is configured as an empty string, the system default will be applied.
-->
- <string name="config_tcp_buffers" translatable="false"></string>
+ <string name="config_tcp_buffers" translatable="false">2097152,6291456,16777216,512000,2097152,8388608</string>
<java-symbol type="string" name="config_tcp_buffers" />
<!-- What source to use to estimate link upstream and downstream bandwidth capacities.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 443f9a6..5bcef68 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4708,6 +4708,7 @@
<java-symbol type="string" name="config_wearSysUiPackage"/>
<java-symbol type="string" name="config_wearSysUiMainActivity"/>
+ <java-symbol type="string" name="config_defaultQrCodeComponent"/>
<java-symbol type="dimen" name="secondary_rounded_corner_radius" />
<java-symbol type="dimen" name="secondary_rounded_corner_radius_top" />
@@ -4753,7 +4754,10 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
- <java-symbol type="color" name="camera_privacy_light"/>
+ <java-symbol type="color" name="camera_privacy_light_day"/>
+ <java-symbol type="color" name="camera_privacy_light_night"/>
+ <java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/>
+ <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/>
<java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
<java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index c9a18da..c9e02f8 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -38,8 +38,8 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.PasswordValidationError;
@@ -324,9 +324,59 @@
PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
}
+ @Test
+ public void testValidatePasswordMetrics_pinAndComplexityHigh() {
+ PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+ PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+ actualMetrics.length = 6;
+ actualMetrics.seqLength = 1;
+
+ assertValidationErrors(
+ validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics),
+ PasswordValidationError.TOO_SHORT, 8);
+ }
+
+ @Test
+ public void testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh() {
+ PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ actualMetrics.length = 5;
+ actualMetrics.nonNumeric = 1;
+ actualMetrics.seqLength = 1;
+
+ assertValidationErrors(
+ validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics),
+ PasswordValidationError.TOO_SHORT, 6);
+ }
+
+ @Test
+ public void testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh() {
+ PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ actualMetrics.length = 6;
+ actualMetrics.seqLength = 1;
+
+ assertValidationErrors(
+ validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics),
+ PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC, 8);
+ }
+
+ @Test
+ public void testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric() {
+ PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ adminMetrics.nonNumeric = 1;
+ PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ actualMetrics.length = 6;
+ actualMetrics.seqLength = 1;
+
+ assertValidationErrors(
+ validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics),
+ PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1);
+ }
+
/**
* @param expected sequense of validation error codes followed by requirement values, must have
- * even number of elements. Empty means no errors.
+ * even number of elements. Empty means no errors.
*/
private void assertValidationErrors(
List<PasswordValidationError> actualErrors, int... expected) {
diff --git a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
index 969357f..22feecb 100644
--- a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
@@ -24,7 +24,6 @@
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.Set;
@@ -32,7 +31,6 @@
@Presubmit
public class AppSearchShortcutInfoTest {
- @Ignore("b/208375334")
@Test
public void testBuildShortcutAndGetValue() {
final String category =
@@ -51,7 +49,7 @@
final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(/*packageName=*/"", id)
.setActivity(activity)
- .setLongLabel(id)
+ .setShortLabel(id)
.setIconResName(shortcutIconResName)
.setIntent(shortcutIntent)
.setPerson(person)
@@ -64,11 +62,13 @@
assertThat(shortcut.getId()).isEqualTo(id);
assertThat(shortcut.getShortLabel()).isEqualTo(id);
assertThat(shortcut.getIconResName()).isEqualTo(shortcutIconResName);
- assertThat(shortcut.getIntent().toString()).isEqualTo(shortcut.toString());
+ assertThat(shortcut.getIntent().toString()).isEqualTo(shortcutIntent.toString());
assertThat(shortcut.getPersons().length).isEqualTo(1);
- assertThat(shortcut.getPersons()[0]).isEqualTo(person);
+ final Person target = shortcut.getPersons()[0];
+ assertThat(target.getName()).isEqualTo(person.getName());
+ assertThat(target.isBot()).isEqualTo(person.isBot());
+ assertThat(target.isImportant()).isEqualTo(person.isImportant());
assertThat(shortcut.getCategories()).isEqualTo(categorySet);
- assertThat(shortcut.getFlags()).isEqualTo(ShortcutInfo.FLAG_LONG_LIVED);
assertThat(shortcut.getActivity()).isEqualTo(activity);
}
}
diff --git a/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java b/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java
index 786c22b..9b6bcda 100644
--- a/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java
+++ b/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java
@@ -19,17 +19,24 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.Context;
+import android.widget.FrameLayout;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
+import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@MediumTest
public class RenderNodeAnimatorTest {
@Rule
@@ -57,4 +64,46 @@
anim.start(); // should initialize mTransformationInfo
assertNotNull(view.mTransformationInfo);
}
+
+ @Test
+ public void testViewDetachCancelsRenderNodeAnimator() {
+ // Start a RenderNodeAnimator with a long duration time, then detach the target view
+ // before the animation completes. Detaching of a View from a window should force cancel all
+ // RenderNodeAnimators
+ CountDownLatch latch = new CountDownLatch(1);
+
+ FrameLayout container = new FrameLayout(getContext());
+ View view = new View(getContext());
+
+ getActivity().runOnUiThread(() -> {
+ container.addView(view, new FrameLayout.LayoutParams(100, 100));
+ getActivity().setContentView(container);
+ });
+ getActivity().runOnUiThread(() -> {
+ RenderNodeAnimator anim = new RenderNodeAnimator(0, 0, 10f, 30f);
+ anim.setDuration(10000);
+ anim.setTarget(view);
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ latch.countDown();
+ }
+ });
+
+ anim.start();
+ });
+
+ getActivity().runOnUiThread(()-> {
+ container.removeView(view);
+ });
+
+ try {
+ Assert.assertTrue("onAnimationEnd not invoked",
+ latch.await(3000, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException excep) {
+ Assert.fail("Interrupted waiting for onAnimationEnd callback");
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
index 2ecc261..0dca638 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
@@ -44,6 +44,7 @@
// share completed fields
public int targetType;
public int positionPicked;
+ public boolean isPinned;
CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
String packageName, InstanceId instanceId) {
@@ -68,12 +69,13 @@
}
CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
- int positionPicked) {
+ int positionPicked, boolean isPinned) {
this.atomId = atomId;
this.packageName = packageName;
this.instanceId = instanceId;
this.targetType = targetType;
this.positionPicked = positionPicked;
+ this.isPinned = isPinned;
}
}
@@ -112,9 +114,11 @@
}
@Override
- public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned) {
mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
- SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked));
+ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked,
+ isPinned));
}
@Override
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 0c009a0..4cf9c3f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -39,6 +39,10 @@
static final OverrideData sOverrides = new OverrideData();
private UsageStatsManager mUsm;
+ public ResolverWrapperActivity() {
+ super(/* isIntentPicker= */ true);
+ }
+
@Override
public ResolverListAdapter createResolverListAdapter(Context context,
List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
index 2262c05..3858792 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
@@ -35,6 +35,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SuppressWarnings("GuardedBy")
public class BatteryStatsHistoryIteratorTest {
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
@@ -124,7 +125,10 @@
// More than 32k strings
final int eventCount = 0x7FFF + 100;
for (int i = 0; i < eventCount; i++) {
- mBatteryStats.noteAlarmStartLocked("a" + i, null, APP_UID, 3_000_000, 2_000_000);
+ // Names repeat in order to verify de-duping of identical history tags.
+ String name = "a" + (i % 10);
+ mBatteryStats.noteAlarmStartLocked(name, null, APP_UID, 3_000_000, 2_000_000);
+ mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID, 3_500_000, 2_500_000);
}
final BatteryStatsHistoryIterator iterator =
@@ -149,10 +153,23 @@
assertThat(item.time).isEqualTo(2_000_000);
for (int i = 0; i < eventCount; i++) {
+ String name = "a" + (i % 10);
assertThat(iterator.next(item)).isTrue();
+ // Skip a blank event inserted at the start of every buffer
+ if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
+ assertThat(iterator.next(item)).isTrue();
+ }
assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM
| BatteryStats.HistoryItem.EVENT_FLAG_START);
- assertThat(item.eventTag.string).isEqualTo("a" + i);
+ assertThat(item.eventTag.string).isEqualTo(name);
+
+ assertThat(iterator.next(item)).isTrue();
+ if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
+ assertThat(iterator.next(item)).isTrue();
+ }
+ assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH);
+ assertThat(item.eventTag.string).isEqualTo(name);
}
assertThat(iterator.next(item)).isFalse();
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ebf5832..f030d80 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -80,5 +80,6 @@
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<permission name="android.permission.READ_DEVICE_CONFIG" />
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0e8388b..12d3d64 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2581,12 +2581,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "397105698": {
- "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found",
- "level": "VERBOSE",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"397382873": {
"message": "Moving to PAUSED: %s %s",
"level": "VERBOSE",
@@ -3115,6 +3109,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "958338552": {
+ "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"959486822": {
"message": "setSyncGroup #%d on %s",
"level": "VERBOSE",
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 13b4096..54f8808 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -44,33 +44,37 @@
key 11 0
# custom keys
-key usage 0x000c019C PROFILE_SWITCH
-key usage 0x000c01A2 ALL_APPS
key usage 0x000c01BB TV_INPUT
-key usage 0x000c022A BOOKMARK
-key usage 0x000c0096 SETTINGS
-key usage 0x000c009F NOTIFICATION
-key usage 0x000c008D GUIDE
-key usage 0x000c0089 TV
-key usage 0x000c009C CHANNEL_UP
-key usage 0x000c009D CHANNEL_DOWN
-key usage 0x000c00CD MEDIA_PLAY_PAUSE
-key usage 0x000c00B2 MEDIA_RECORD
-key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
-key usage 0x000c00B3 MEDIA_SKIP_FORWARD
-key usage 0x000c0226 MEDIA_STOP
-key usage 0x000c0077 BUTTON_3 WAKE #YouTube
-key usage 0x000c0078 BUTTON_4 WAKE #Netflix
-key usage 0x000c0079 BUTTON_6 WAKE #Disney+
-key usage 0x000c007A BUTTON_7 WAKE #HBOmax
-
-key usage 0x00070037 PERIOD
-key usage 0x000c01BD INFO
-key usage 0x000c0061 CAPTIONS
key usage 0x000c0185 TV_TELETEXT
+key usage 0x000c0061 CAPTIONS
+
+key usage 0x000c01BD INFO
+key usage 0x000c0037 PERIOD
key usage 0x000c0069 PROG_RED
key usage 0x000c006A PROG_GREEN
+key usage 0x000c006C PROG_YELLOW
key usage 0x000c006B PROG_BLUE
-key usage 0x000c006C PROG_YELLOW
\ No newline at end of file
+key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
+key usage 0x000c00CD MEDIA_PLAY_PAUSE
+key usage 0x000c00B2 MEDIA_RECORD
+key usage 0x000c00B3 MEDIA_SKIP_FORWARD
+
+key usage 0x000c022A BOOKMARK
+key usage 0x000c01A2 ALL_APPS
+key usage 0x000c019C PROFILE_SWITCH
+
+key usage 0x000c0096 SETTINGS
+key usage 0x000c009F NOTIFICATION
+
+key usage 0x000c008D GUIDE
+key usage 0x000c0089 TV
+
+key usage 0x000c009C CHANNEL_UP
+key usage 0x000c009D CHANNEL_DOWN
+
+key usage 0x000c0077 BUTTON_3 WAKE #YouTube
+key usage 0x000c0078 BUTTON_4 WAKE #Netflix
+key usage 0x000c0079 BUTTON_6 WAKE
+key usage 0x000c007A BUTTON_7 WAKE
\ No newline at end of file
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 5fd53ad..dadbd8d 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -1611,6 +1611,11 @@
nEndAllAnimators(mNativeRenderNode);
}
+ /** @hide */
+ public void forceEndAnimators() {
+ nForceEndAnimators(mNativeRenderNode);
+ }
+
///////////////////////////////////////////////////////////////////////////
// Regular JNI methods
///////////////////////////////////////////////////////////////////////////
@@ -1633,6 +1638,8 @@
private static native void nEndAllAnimators(long renderNode);
+ private static native void nForceEndAnimators(long renderNode);
+
///////////////////////////////////////////////////////////////////////////
// @CriticalNative methods
///////////////////////////////////////////////////////////////////////////
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java
new file mode 100644
index 0000000..4855ad0
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java
@@ -0,0 +1,46 @@
+/*
+ * 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.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyDescriptor;
+
+import java.security.PrivateKey;
+import java.security.interfaces.EdECKey;
+import java.security.spec.NamedParameterSpec;
+
+/**
+ * EdEC private key (instance of {@link PrivateKey} and {@link EdECKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreEdECPrivateKey extends AndroidKeyStorePrivateKey implements EdECKey {
+ public AndroidKeyStoreEdECPrivateKey(
+ @NonNull KeyDescriptor descriptor, long keyId,
+ @NonNull Authorization[] authorizations,
+ @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel securityLevel) {
+ super(descriptor, keyId, authorizations, algorithm, securityLevel);
+ }
+
+ @Override
+ public NamedParameterSpec getParams() {
+ return NamedParameterSpec.ED25519;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java
new file mode 100644
index 0000000..642e088
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java
@@ -0,0 +1,145 @@
+/*
+ * 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.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import java.math.BigInteger;
+import java.security.interfaces.EdECPublicKey;
+import java.security.spec.EdECPoint;
+import java.security.spec.NamedParameterSpec;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * {@link EdECPublicKey} backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreEdECPublicKey extends AndroidKeyStorePublicKey
+ implements EdECPublicKey {
+ /**
+ * DER sequence, as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-4 and
+ * https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.
+ * SEQUENCE (2 elem)
+ * SEQUENCE (1 elem)
+ * OBJECT IDENTIFIER 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm)
+ * as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-3
+ * BIT STRING (256 bit) as defined in
+ * https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
+ */
+ private static final byte[] DER_KEY_PREFIX = new byte[] {
+ 0x30,
+ 0x2a,
+ 0x30,
+ 0x05,
+ 0x06,
+ 0x03,
+ 0x2b,
+ 0x65,
+ 0x70,
+ 0x03,
+ 0x21,
+ 0x00,
+ };
+ private static final int ED25519_KEY_SIZE_BYTES = 32;
+
+ private byte[] mEncodedKey;
+ private EdECPoint mPoint;
+
+ public AndroidKeyStoreEdECPublicKey(
+ @NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel iSecurityLevel,
+ @NonNull byte[] encodedKey) {
+ super(descriptor, metadata, encodedKey, algorithm, iSecurityLevel);
+ mEncodedKey = encodedKey;
+
+ int preambleLength = matchesPreamble(DER_KEY_PREFIX, encodedKey);
+ if (preambleLength == 0) {
+ throw new IllegalArgumentException("Key size is not correct size");
+ }
+
+ mPoint = pointFromKeyByteArray(
+ Arrays.copyOfRange(encodedKey, preambleLength, encodedKey.length));
+ }
+
+ @Override
+ AndroidKeyStorePrivateKey getPrivateKey() {
+ return new AndroidKeyStoreEdECPrivateKey(
+ getUserKeyDescriptor(),
+ getKeyIdDescriptor().nspace,
+ getAuthorizations(),
+ "EdDSA",
+ getSecurityLevel());
+ }
+
+ @Override
+ public NamedParameterSpec getParams() {
+ return NamedParameterSpec.ED25519;
+ }
+
+ @Override
+ public EdECPoint getPoint() {
+ return mPoint;
+ }
+
+ private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+ if (encoded.length != (preamble.length + ED25519_KEY_SIZE_BYTES)) {
+ return 0;
+ }
+ if (Arrays.compare(preamble, Arrays.copyOf(encoded, preamble.length)) != 0) {
+ return 0;
+ }
+ return preamble.length;
+ }
+
+ private static EdECPoint pointFromKeyByteArray(byte[] coordinates) {
+ Objects.requireNonNull(coordinates);
+
+ // Oddity of the key is the most-significant bit of the last byte.
+ boolean isOdd = (0x80 & coordinates[coordinates.length - 1]) != 0;
+ // Zero out the oddity bit.
+ coordinates[coordinates.length - 1] &= (byte) 0x7f;
+ // Representation of Y is in little-endian, according to rfc8032 section-3.1.
+ reverse(coordinates);
+ // The integer representing Y starts from the first bit in the coordinates array.
+ BigInteger y = new BigInteger(1, coordinates);
+ return new EdECPoint(isOdd, y);
+ }
+
+ private static void reverse(byte[] coordinateArray) {
+ int start = 0;
+ int end = coordinateArray.length - 1;
+ while (start < end) {
+ byte tmp = coordinateArray[start];
+ coordinateArray[start] = coordinateArray[end];
+ coordinateArray[end] = tmp;
+ start++;
+ end--;
+ }
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return mEncodedKey.clone();
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index d31499e..0355628 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -224,7 +224,6 @@
String jcaKeyAlgorithm = publicKey.getAlgorithm();
- KeyStoreSecurityLevel securityLevel = iSecurityLevel;
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
return new AndroidKeyStoreECPublicKey(descriptor, metadata,
iSecurityLevel, (ECPublicKey) publicKey);
@@ -232,8 +231,9 @@
return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
iSecurityLevel, (RSAPublicKey) publicKey);
} else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) {
- //TODO(b/214203951) missing classes in conscrypt
- throw new ProviderException("Curve " + ED25519_OID + " not supported yet");
+ final byte[] publicKeyEncoded = publicKey.getEncoded();
+ return new AndroidKeyStoreEdECPublicKey(descriptor, metadata, ED25519_OID,
+ iSecurityLevel, publicKeyEncoded);
} else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) {
//TODO(b/214203951) missing classes in conscrypt
throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet");
diff --git a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java
new file mode 100644
index 0000000..5bd5797
--- /dev/null
+++ b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.security.keystore2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.math.BigInteger;
+import java.util.Base64;
+
+@RunWith(AndroidJUnit4.class)
+public class AndroidKeyStoreEdECPublicKeyTest {
+ private static KeyDescriptor descriptor() {
+ final KeyDescriptor keyDescriptor = new KeyDescriptor();
+ keyDescriptor.alias = "key";
+ keyDescriptor.blob = null;
+ keyDescriptor.domain = Domain.APP;
+ keyDescriptor.nspace = -1;
+ return keyDescriptor;
+ }
+
+ private static KeyMetadata metadata(byte[] cert, byte[] certChain) {
+ KeyMetadata metadata = new KeyMetadata();
+ metadata.authorizations = new Authorization[0];
+ metadata.certificate = cert;
+ metadata.certificateChain = certChain;
+ metadata.key = descriptor();
+ metadata.modificationTimeMs = 0;
+ metadata.keySecurityLevel = 1;
+ return metadata;
+ }
+
+ @Mock
+ private KeyStoreSecurityLevel mKeystoreSecurityLevel;
+
+ private static class EdECTestVector {
+ public final byte[] encodedKeyBytes;
+ public final boolean isOdd;
+ public final BigInteger yValue;
+
+ EdECTestVector(String b64KeyBytes, boolean isOdd, String yValue) {
+ this.encodedKeyBytes = Base64.getDecoder().decode(b64KeyBytes);
+ this.isOdd = isOdd;
+ this.yValue = new BigInteger(yValue);
+ }
+ }
+
+ private static final EdECTestVector[] ED_EC_TEST_VECTORS = new EdECTestVector[]{
+ new EdECTestVector("MCowBQYDK2VwAyEADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSo=",
+ false,
+ "19147682157189290216699341180089409126316261024914226007941553249095116672780"
+ ),
+ new EdECTestVector("MCowBQYDK2VwAyEA/0E1IRNzGj85Ot/TPeXqifkqTkdk4voleH0hIq59D9w=",
+ true,
+ "41640152188550647350742178040529506688513911269563908889464821205156322689535"
+ ),
+ new EdECTestVector("MCowBQYDK2VwAyEAunOvGuenetl9GQSXGVo5L3RIr4OOIpFIv/Zre8qTc/8=",
+ true,
+ "57647939198144376128225770417635248407428273266444593100194116168980378907578"
+ ),
+ new EdECTestVector("MCowBQYDK2VwAyEA2hHqaZ5IolswN1Yd58Y4hzhmUMCCqc4PW5A/SFLmTX8=",
+ false,
+ "57581368614046789120409806291852629847774713088410311752049592044694364885466"
+ ),
+ };
+
+ @Test
+ public void testParsingOfValidKeys() {
+ for (EdECTestVector testVector : ED_EC_TEST_VECTORS) {
+ AndroidKeyStoreEdECPublicKey pkey = new AndroidKeyStoreEdECPublicKey(descriptor(),
+ metadata(null, null), "EdDSA", mKeystoreSecurityLevel,
+ testVector.encodedKeyBytes);
+
+ assertEquals(pkey.getPoint().isXOdd(), testVector.isOdd);
+ assertEquals(pkey.getPoint().getY(), testVector.yValue);
+ }
+ }
+
+ @Test
+ public void testFailedParsingOfKeysWithDifferentOid() {
+ final byte[] testVectorWithIncorrectOid = Base64.getDecoder().decode(
+ "MCowBQYDLGVwAyEADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSo=");
+ assertThrows("OID should be unrecognized", IllegalArgumentException.class,
+ () -> new AndroidKeyStoreEdECPublicKey(descriptor(), metadata(null, null), "EdDSA",
+ mKeystoreSecurityLevel, testVectorWithIncorrectOid));
+ }
+
+ @Test
+ public void testFailedParsingOfKeysWithWrongSize() {
+ final byte[] testVectorWithIncorrectKeySize = Base64.getDecoder().decode(
+ "MCwwBQYDK2VwAyMADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSrOzg==");
+ assertThrows("Key length should be invalid", IllegalArgumentException.class,
+ () -> new AndroidKeyStoreEdECPublicKey(descriptor(), metadata(null, null), "EdDSA",
+ mKeystoreSecurityLevel, testVectorWithIncorrectKeySize));
+ }
+}
+
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index faada1a..9713c273 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -422,6 +422,18 @@
launchPlaceholderIfNecessary(activity);
}
+ @VisibleForTesting
+ void onActivityDestroyed(@NonNull Activity activity) {
+ // Remove any pending appeared activity, as the server won't send finished activity to the
+ // organizer.
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ mTaskContainers.valueAt(i).cleanupPendingAppearedActivity(activity);
+ }
+ // We didn't trigger the callback if there were any pending appeared activities, so check
+ // again after the pending is removed.
+ updateCallbackIfNecessary();
+ }
+
/**
* Called when we have been waiting too long for the TaskFragment to become non-empty after
* creation.
@@ -465,12 +477,12 @@
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
- final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId, this);
if (!mTaskContainers.contains(taskId)) {
mTaskContainers.put(taskId, new TaskContainer(taskId));
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
- taskContainer.mContainers.add(container);
+ final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskContainer,
+ this);
if (!taskContainer.isTaskBoundsInitialized()) {
// Get the initial bounds before the TaskFragment has appeared.
final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
@@ -500,14 +512,13 @@
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
}
- mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer);
+ primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- final int taskId = container.getTaskId();
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
+ final TaskContainer taskContainer = container.getTaskContainer();
if (taskContainer == null) {
return;
}
@@ -545,8 +556,7 @@
*/
void removeContainer(@NonNull TaskFragmentContainer container) {
// Remove all split containers that included this one
- final int taskId = container.getTaskId();
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
+ final TaskContainer taskContainer = container.getTaskContainer();
if (taskContainer == null) {
return;
}
@@ -637,8 +647,7 @@
if (splitContainer == null) {
return;
}
- final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
- .mSplitContainers;
+ final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
if (splitContainer != splitContainers.get(splitContainers.size() - 1)) {
// Skip position update - it isn't the topmost split.
return;
@@ -660,8 +669,7 @@
*/
@Nullable
private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
- final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
- .mSplitContainers;
+ final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
if (splitContainers.isEmpty()) {
return null;
}
@@ -683,11 +691,8 @@
private SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer,
@NonNull TaskFragmentContainer secondContainer) {
- final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId())
+ final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
.mSplitContainers;
- if (splitContainers == null) {
- return null;
- }
for (int i = splitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = splitContainers.get(i);
final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -856,11 +861,7 @@
if (container == null) {
return false;
}
- final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
- .mSplitContainers;
- if (splitContainers == null) {
- return true;
- }
+ final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
for (SplitContainer splitContainer : splitContainers) {
if (container.equals(splitContainer.getPrimaryContainer())
|| container.equals(splitContainer.getSecondaryContainer())) {
@@ -1046,6 +1047,11 @@
public void onActivityConfigurationChanged(Activity activity) {
SplitController.this.onActivityConfigurationChanged(activity);
}
+
+ @Override
+ public void onActivityPostDestroyed(Activity activity) {
+ SplitController.this.onActivityDestroyed(activity);
+ }
}
/** Executor that posts on the main application thread. */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 06c1d4e..b32f4fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -291,8 +291,7 @@
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- final TaskContainer taskContainer = mController.getTaskContainer(
- updatedContainer.getTaskId());
+ final TaskContainer taskContainer = updatedContainer.getTaskContainer();
final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
primaryRectBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
@@ -456,12 +455,7 @@
@NonNull
Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
- final int taskId = container.getTaskId();
- final TaskContainer taskContainer = mController.getTaskContainer(taskId);
- if (taskContainer == null) {
- throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId);
- }
- return taskContainer.getTaskBounds();
+ return container.getTaskContainer().getTaskBounds();
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 5762843..dba71ef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -16,12 +16,14 @@
package androidx.window.extensions.embedding;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
@@ -62,6 +64,9 @@
final Set<IBinder> mFinishedContainer = new ArraySet<>();
TaskContainer(int taskId) {
+ if (taskId == INVALID_TASK_ID) {
+ throw new IllegalArgumentException("Invalid Task id");
+ }
mTaskId = taskId;
}
@@ -130,4 +135,11 @@
boolean isEmpty() {
return mContainers.isEmpty() && mFinishedContainer.isEmpty();
}
+
+ /** Removes the pending appeared activity from all TaskFragments in this Task. */
+ void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ for (TaskFragmentContainer container : mContainers) {
+ container.removePendingAppearedActivity(pendingAppearedActivity);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 26bbcbb..6693755 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -16,7 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.annotation.NonNull;
@@ -52,8 +51,9 @@
@NonNull
private final IBinder mToken;
- /** Parent leaf Task id. */
- private final int mTaskId;
+ /** Parent leaf Task. */
+ @NonNull
+ private final TaskContainer mTaskContainer;
/**
* Server-provided task fragment information.
@@ -100,14 +100,12 @@
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
*/
- TaskFragmentContainer(@Nullable Activity activity, int taskId,
+ TaskFragmentContainer(@Nullable Activity activity, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller) {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
- if (taskId == INVALID_TASK_ID) {
- throw new IllegalArgumentException("Invalid Task id");
- }
- mTaskId = taskId;
+ mTaskContainer = taskContainer;
+ taskContainer.mContainers.add(this);
if (activity != null) {
addPendingAppearedActivity(activity);
}
@@ -162,9 +160,18 @@
}
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ if (hasActivity(pendingAppearedActivity.getActivityToken())) {
+ return;
+ }
+ // Remove the pending activity from other TaskFragments.
+ mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
mPendingAppearedActivities.add(pendingAppearedActivity);
}
+ void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ mPendingAppearedActivities.remove(pendingAppearedActivity);
+ }
+
boolean hasActivity(@NonNull IBinder token) {
if (mInfo != null && mInfo.getActivities().contains(token)) {
return true;
@@ -376,7 +383,13 @@
/** Gets the parent leaf Task id. */
int getTaskId() {
- return mTaskId;
+ return mTaskContainer.getTaskId();
+ }
+
+ /** Gets the parent Task. */
+ @NonNull
+ TaskContainer getTaskContainer() {
+ return mTaskContainer;
}
@Override
@@ -392,6 +405,7 @@
*/
private String toString(boolean includeContainersToFinishOnExit) {
return "TaskFragmentContainer{"
+ + " parentTaskId=" + getTaskId()
+ " token=" + mToken
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 7aa47ef..792a531 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -113,8 +113,9 @@
@Test
public void testExpandTaskFragment() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- TASK_ID, mSplitController);
+ taskContainer, mSplitController);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
container.setInfo(info);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 983208c..34cde9b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -22,8 +22,10 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -35,6 +37,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
@@ -96,20 +99,18 @@
@Test
public void testGetTopActiveContainer() {
- TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ // tf1 has no running activity so is not active.
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
+ taskContainer, mSplitController);
+ // tf2 has running activity so is active.
+ final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
+ doReturn(1).when(tf2).getRunningActivityCount();
+ taskContainer.mContainers.add(tf2);
// tf3 is finished so is not active.
- TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
+ final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
doReturn(true).when(tf3).isFinished();
doReturn(false).when(tf3).isWaitingActivityAppear();
- // tf2 has running activity so is active.
- TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- // tf1 has no running activity so is not active.
- TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, TASK_ID,
- mSplitController);
-
- taskContainer.mContainers.add(tf1);
- taskContainer.mContainers.add(tf2);
taskContainer.mContainers.add(tf3);
mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
@@ -164,6 +165,18 @@
}
@Test
+ public void testOnActivityDestroyed() {
+ doReturn(new Binder()).when(mActivity).getActivityToken();
+ final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
+
+ assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+
+ mSplitController.onActivityDestroyed(mActivity);
+
+ assertFalse(tf.hasActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
public void testNewContainer() {
// Must pass in a valid activity.
assertThrows(IllegalArgumentException.class, () ->
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index c40bab8..0de94b0 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -137,9 +137,8 @@
assertTrue(taskContainer.isEmpty());
- final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, TASK_ID,
- mController);
- taskContainer.mContainers.add(tf);
+ final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
+ taskContainer, mController);
assertFalse(taskContainer.isEmpty());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index d80f2b9..ce80cbf3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -76,7 +76,8 @@
@Test
public void testFinish() {
- final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, TASK_ID,
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, taskContainer,
mController);
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -107,8 +108,9 @@
@Test
public void testIsWaitingActivityAppear() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- TASK_ID, mController);
+ taskContainer, mController);
assertTrue(container.isWaitingActivityAppear());
@@ -127,8 +129,9 @@
@Test
public void testAppearEmptyTimeout() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- TASK_ID, mController);
+ taskContainer, mController);
assertNull(container.mAppearEmptyTimeout);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index c3fbe55..8fa9f56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -246,9 +246,13 @@
* {@link BackAnimationController}
*/
public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) {
- if (action == MotionEvent.ACTION_DOWN) {
- initAnimation(event);
- } else if (action == MotionEvent.ACTION_MOVE) {
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (!mBackGestureStarted) {
+ // Let the animation initialized here to make sure the onPointerDownOutsideFocus
+ // could be happened when ACTION_DOWN, it may change the current focus that we
+ // would access it when startBackNavigation.
+ initAnimation(event);
+ }
onMove(event, swipeEdge);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 3876533..b04a1fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Path;
@@ -350,16 +351,19 @@
}
void showBadge() {
- if (mBubble.getAppBadge() == null) {
+ Bitmap appBadgeBitmap = mBubble.getAppBadge();
+ if (appBadgeBitmap == null) {
mAppIcon.setVisibility(GONE);
return;
}
+
int translationX;
if (mOnLeft) {
- translationX = -(mBubbleIcon.getWidth() - mAppIcon.getWidth());
+ translationX = -(mBubble.getBubbleIcon().getWidth() - appBadgeBitmap.getWidth());
} else {
translationX = 0;
}
+
mAppIcon.setTranslationX(translationX);
mAppIcon.setVisibility(VISIBLE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 145e527..1ea5e21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.Handler;
+import android.os.SystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -39,6 +40,7 @@
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
+import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
@@ -64,6 +66,7 @@
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
@@ -74,13 +77,13 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
+ @ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(
TvPipController.create(
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ tvPipBoundsController,
pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
@@ -91,8 +94,22 @@
pipParamsChangedForwarder,
displayController,
windowManagerShellWrapper,
- mainExecutor,
- mainHandler));
+ mainExecutor));
+ }
+
+ @WMSingleton
+ @Provides
+ static TvPipBoundsController provideTvPipBoundsController(
+ Context context,
+ @ShellMainThread Handler mainHandler,
+ TvPipBoundsState tvPipBoundsState,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm) {
+ return new TvPipBoundsController(
+ context,
+ SystemClock::uptimeMillis,
+ mainHandler,
+ tvPipBoundsState,
+ tvPipBoundsAlgorithm);
}
@WMSingleton
@@ -143,8 +160,11 @@
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
+ TvPipBoundsState tvPipBoundsState,
@ShellMainThread Handler mainHandler) {
- return new TvPipNotificationController(context, pipMediaController, mainHandler);
+ return new TvPipNotificationController(context, pipMediaController,
+ pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java
index f8f9d6b..65cb7ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java
@@ -16,13 +16,18 @@
package com.android.wm.shell.kidsmode;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import java.util.Collection;
+
/**
* A ContentObserver for listening kids mode relative setting keys:
* - {@link Settings.Secure#NAVIGATION_MODE}
@@ -64,7 +69,11 @@
}
@Override
- public void onChange(boolean selfChange) {
+ public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags, int userId) {
+ if (userId != ActivityManager.getCurrentUser()) {
+ return;
+ }
+
if (mOnChangeRunnable != null) {
mOnChangeRunnable.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index dc70358..b4c87b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -23,7 +23,10 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -87,6 +90,13 @@
private KidsModeSettingsObserver mKidsModeSettingsObserver;
private boolean mEnabled;
+ private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateKidsModeState();
+ }
+ };
+
DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
@@ -169,12 +179,15 @@
public void initialize(StartingWindowController startingWindowController) {
initStartingWindow(startingWindowController);
if (mKidsModeSettingsObserver == null) {
- mKidsModeSettingsObserver = new KidsModeSettingsObserver(
- mMainHandler, mContext);
+ mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
mKidsModeSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState());
updateKidsModeState();
mKidsModeSettingsObserver.register();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiverForAllUsers(mUserSwitchIntentReceiver, filter, null, mMainHandler);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index c0734e9..3b3091a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -44,11 +44,6 @@
}
/**
- * Hides the PIP menu.
- */
- default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
-
- /**
* Called when configuration is changed.
*/
default void onConfigurationChanged(Configuration newConfig) {
@@ -125,6 +120,23 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
+ * Called when the visibility of keyguard is changed.
+ * @param showing {@code true} if keyguard is now showing, {@code false} otherwise.
+ * @param animating {@code true} if system is animating between keyguard and surface behind,
+ * this only makes sense when showing is {@code false}.
+ */
+ default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { }
+
+ /**
+ * Called when the dismissing animation keyguard and surfaces behind is finished.
+ * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}.
+ *
+ * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
+ * keyguard dismiss animation.
+ */
+ default void onKeyguardDismissAnimationFinished() { }
+
+ /**
* Dump the current state and information if need.
*
* @param pw The stream to dump information to.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 8a50f22..65a12d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -32,6 +32,7 @@
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Handler;
@@ -64,7 +65,7 @@
*/
public interface ActionListener {
/**
- * Called when the media actions changes.
+ * Called when the media actions changed.
*/
void onMediaActionsChanged(List<RemoteAction> actions);
}
@@ -74,11 +75,21 @@
*/
public interface MetadataListener {
/**
- * Called when the media metadata changes.
+ * Called when the media metadata changed.
*/
void onMediaMetadataChanged(MediaMetadata metadata);
}
+ /**
+ * A listener interface to receive notification on changes to the media session token.
+ */
+ public interface TokenListener {
+ /**
+ * Called when the media session token changed.
+ */
+ void onMediaSessionTokenChanged(MediaSession.Token token);
+ }
+
private final Context mContext;
private final Handler mMainHandler;
private final HandlerExecutor mHandlerExecutor;
@@ -133,6 +144,7 @@
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
+ private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
public PipMediaController(Context context, Handler mainHandler) {
mContext = context;
@@ -204,6 +216,31 @@
mMetadataListeners.remove(listener);
}
+ /**
+ * Adds a new token listener.
+ */
+ public void addTokenListener(TokenListener listener) {
+ if (!mTokenListeners.contains(listener)) {
+ mTokenListeners.add(listener);
+ listener.onMediaSessionTokenChanged(getToken());
+ }
+ }
+
+ /**
+ * Removes a token listener.
+ */
+ public void removeTokenListener(TokenListener listener) {
+ listener.onMediaSessionTokenChanged(null);
+ mTokenListeners.remove(listener);
+ }
+
+ private MediaSession.Token getToken() {
+ if (mMediaController == null) {
+ return null;
+ }
+ return mMediaController.getSessionToken();
+ }
+
private MediaMetadata getMediaMetadata() {
return mMediaController != null ? mMediaController.getMetadata() : null;
}
@@ -294,6 +331,7 @@
}
notifyActionsChanged();
notifyMetadataChanged(getMediaMetadata());
+ notifyTokenChanged(getToken());
// TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
}
@@ -317,4 +355,10 @@
mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata));
}
}
+
+ private void notifyTokenChanged(MediaSession.Token token) {
+ if (!mTokenListeners.isEmpty()) {
+ mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 4690e16..45c9a95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -965,6 +965,17 @@
mDeferredAnimEndTransaction = null;
}
+ /** Explicitly set the visibility of PiP window. */
+ public void setPipVisibility(boolean visible) {
+ if (!isInPip()) {
+ return;
+ }
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper.alpha(tx, mLeash, visible ? 1f : 0f);
+ tx.apply();
+ }
+
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
mCurrentRotation = newConfig.windowConfiguration.getRotation();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 42ceb42..dad261a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -129,6 +129,8 @@
protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener =
new PipControllerPinnedTaskListener();
+ private boolean mIsKeyguardShowingOrAnimating;
+
private interface PipAnimationListener {
/**
* Notifies the listener that the Pip animation is started.
@@ -593,6 +595,33 @@
}
/**
+ * If {@param keyguardShowing} is {@code false} and {@param animating} is {@code true},
+ * we would wait till the dismissing animation of keyguard and surfaces behind to be
+ * finished first to reset the visibility of PiP window.
+ * See also {@link #onKeyguardDismissAnimationFinished()}
+ */
+ private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) {
+ if (!mPipTaskOrganizer.isInPip()) {
+ return;
+ }
+ if (keyguardShowing) {
+ mIsKeyguardShowingOrAnimating = true;
+ hidePipMenu(null /* onStartCallback */, null /* onEndCallback */);
+ mPipTaskOrganizer.setPipVisibility(false);
+ } else if (!animating) {
+ mIsKeyguardShowingOrAnimating = false;
+ mPipTaskOrganizer.setPipVisibility(true);
+ }
+ }
+
+ private void onKeyguardDismissAnimationFinished() {
+ if (mPipTaskOrganizer.isInPip()) {
+ mIsKeyguardShowingOrAnimating = false;
+ mPipTaskOrganizer.setPipVisibility(true);
+ }
+ }
+
+ /**
* Sets a customized touch gesture that replaces the default one.
*/
public void setTouchGesture(PipTouchGesture gesture) {
@@ -603,7 +632,9 @@
* Sets both shelf visibility and its height.
*/
private void setShelfHeight(boolean visible, int height) {
- setShelfHeightLocked(visible, height);
+ if (!mIsKeyguardShowingOrAnimating) {
+ setShelfHeightLocked(visible, height);
+ }
}
private void setShelfHeightLocked(boolean visible, int height) {
@@ -834,13 +865,6 @@
}
@Override
- public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
- mMainExecutor.execute(() -> {
- PipController.this.hidePipMenu(onStartCallback, onEndCallback);
- });
- }
-
- @Override
public void expandPip() {
mMainExecutor.execute(() -> {
PipController.this.expandPip();
@@ -918,6 +942,18 @@
}
@Override
+ public void onKeyguardVisibilityChanged(boolean showing, boolean animating) {
+ mMainExecutor.execute(() -> {
+ PipController.this.onKeyguardVisibilityChanged(showing, animating);
+ });
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished);
+ }
+
+ @Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 21d5d40..a2eadcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Size;
import android.view.Gravity;
@@ -66,7 +65,7 @@
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, tvPipBoundsState, pipSnapAlgorithm);
this.mTvPipBoundsState = tvPipBoundsState;
- this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis);
+ this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
}
@@ -80,7 +79,6 @@
res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding));
mKeepClearAlgorithm.setMaxRestrictedDistanceFraction(
res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1));
- mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration));
}
@Override
@@ -104,7 +102,7 @@
updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
- return getTvPipBounds().getBounds();
+ return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@@ -114,13 +112,27 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
}
- return getTvPipBounds().getBounds();
+ return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
+ }
+
+ Rect adjustBoundsForTemporaryDecor(Rect bounds) {
+ Rect boundsWithDecor = new Rect(bounds);
+ Insets decorInset = mTvPipBoundsState.getPipMenuTemporaryDecorInsets();
+ Insets pipDecorReverseInsets = Insets.subtract(Insets.NONE, decorInset);
+ boundsWithDecor.inset(decorInset);
+ Gravity.apply(mTvPipBoundsState.getTvPipGravity(),
+ boundsWithDecor.width(), boundsWithDecor.height(), bounds, boundsWithDecor);
+
+ // remove temporary decoration again
+ boundsWithDecor.inset(pipDecorReverseInsets);
+ return boundsWithDecor;
}
/**
* Calculates the PiP bounds.
*/
- public Placement getTvPipBounds() {
+ @NonNull
+ public Placement getTvPipPlacement() {
final Size pipSize = getPipSize();
final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
@@ -153,8 +165,6 @@
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
mKeepClearAlgorithm.setPipPermanentDecorInsets(
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
- mKeepClearAlgorithm.setPipTemporaryDecorInsets(
- mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,
@@ -407,8 +417,4 @@
TAG, expandedSize.getWidth(), expandedSize.getHeight());
}
}
-
- void keepUnstashedForCurrentKeepClearAreas() {
- mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
new file mode 100644
index 0000000..3a6ce81
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -0,0 +1,253 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * Controller managing the PiP's position.
+ * Manages debouncing of PiP movements and scheduling of unstashing.
+ */
+public class TvPipBoundsController {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvPipBoundsController";
+
+ /**
+ * Time the calculated PiP position needs to be stable before PiP is moved there,
+ * to avoid erratic movement.
+ * Some changes will cause the PiP to be repositioned immediately, such as changes to
+ * unrestricted keep clear areas.
+ */
+ @VisibleForTesting
+ static final long POSITION_DEBOUNCE_TIMEOUT_MILLIS = 300L;
+
+ private final Context mContext;
+ private final Supplier<Long> mClock;
+ private final Handler mMainHandler;
+ private final TvPipBoundsState mTvPipBoundsState;
+ private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+
+ @Nullable
+ private PipBoundsListener mListener;
+
+ private int mResizeAnimationDuration;
+ private int mStashDurationMs;
+ private Rect mCurrentPlacementBounds;
+ private Rect mPipTargetBounds;
+
+ private final Runnable mApplyPendingPlacementRunnable = this::applyPendingPlacement;
+ private boolean mPendingStash;
+ private Placement mPendingPlacement;
+ private int mPendingPlacementAnimationDuration;
+ private Runnable mUnstashRunnable;
+
+ public TvPipBoundsController(
+ Context context,
+ Supplier<Long> clock,
+ Handler mainHandler,
+ TvPipBoundsState tvPipBoundsState,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm) {
+ mContext = context;
+ mClock = clock;
+ mMainHandler = mainHandler;
+ mTvPipBoundsState = tvPipBoundsState;
+ mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
+
+ loadConfigurations();
+ }
+
+ private void loadConfigurations() {
+ final Resources res = mContext.getResources();
+ mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+ mStashDurationMs = res.getInteger(R.integer.config_pipStashDuration);
+ }
+
+ void setListener(PipBoundsListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Update the PiP bounds based on the state of the PiP, decors, and keep clear areas.
+ * Unless {@code immediate} is {@code true}, the PiP does not move immediately to avoid
+ * keep clear areas, but waits for a new position to stay uncontested for
+ * {@link #POSITION_DEBOUNCE_TIMEOUT_MILLIS} before moving to it.
+ * Temporary decor changes are applied immediately.
+ *
+ * @param stayAtAnchorPosition If true, PiP will be placed at the anchor position
+ * @param disallowStashing If true, PiP will not be placed off-screen in a stashed position
+ * @param animationDuration Duration of the animation to the new position
+ * @param immediate If true, PiP will move immediately to avoid keep clear areas
+ */
+ @VisibleForTesting
+ void recalculatePipBounds(boolean stayAtAnchorPosition, boolean disallowStashing,
+ int animationDuration, boolean immediate) {
+ final Placement placement = mTvPipBoundsAlgorithm.getTvPipPlacement();
+
+ final int stashType = disallowStashing ? STASH_TYPE_NONE : placement.getStashType();
+ mTvPipBoundsState.setStashed(stashType);
+ if (stayAtAnchorPosition) {
+ cancelScheduledPlacement();
+ applyPlacementBounds(placement.getAnchorBounds(), animationDuration);
+ } else if (disallowStashing) {
+ cancelScheduledPlacement();
+ applyPlacementBounds(placement.getUnstashedBounds(), animationDuration);
+ } else if (immediate) {
+ cancelScheduledPlacement();
+ applyPlacementBounds(placement.getBounds(), animationDuration);
+ scheduleUnstashIfNeeded(placement);
+ } else {
+ applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ schedulePinnedStackPlacement(placement, animationDuration);
+ }
+ }
+
+ private void schedulePinnedStackPlacement(@NonNull final Placement placement,
+ int animationDuration) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: schedulePinnedStackPlacement() - pip bounds: %s",
+ TAG, placement.getBounds().toShortString());
+ }
+
+ if (mPendingPlacement != null && Objects.equals(mPendingPlacement.getBounds(),
+ placement.getBounds())) {
+ mPendingStash = mPendingStash || placement.getTriggerStash();
+ return;
+ }
+
+ mPendingStash = placement.getStashType() != STASH_TYPE_NONE
+ && (mPendingStash || placement.getTriggerStash());
+
+ mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable);
+ mPendingPlacement = placement;
+ mPendingPlacementAnimationDuration = animationDuration;
+ mMainHandler.postAtTime(mApplyPendingPlacementRunnable,
+ mClock.get() + POSITION_DEBOUNCE_TIMEOUT_MILLIS);
+ }
+
+ private void scheduleUnstashIfNeeded(final Placement placement) {
+ if (mUnstashRunnable != null) {
+ mMainHandler.removeCallbacks(mUnstashRunnable);
+ mUnstashRunnable = null;
+ }
+ if (placement.getUnstashDestinationBounds() != null) {
+ mUnstashRunnable = () -> {
+ applyPlacementBounds(placement.getUnstashDestinationBounds(),
+ mResizeAnimationDuration);
+ mUnstashRunnable = null;
+ };
+ mMainHandler.postAtTime(mUnstashRunnable, mClock.get() + mStashDurationMs);
+ }
+ }
+
+ private void applyPendingPlacement() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: applyPendingPlacement()", TAG);
+ }
+ if (mPendingPlacement != null) {
+ if (mPendingStash) {
+ mPendingStash = false;
+ scheduleUnstashIfNeeded(mPendingPlacement);
+ }
+
+ if (mUnstashRunnable != null) {
+ // currently stashed, use stashed pos
+ applyPlacementBounds(mPendingPlacement.getBounds(),
+ mPendingPlacementAnimationDuration);
+ } else {
+ applyPlacementBounds(mPendingPlacement.getUnstashedBounds(),
+ mPendingPlacementAnimationDuration);
+ }
+ }
+
+ mPendingPlacement = null;
+ }
+
+ void onPipDismissed() {
+ mCurrentPlacementBounds = null;
+ mPipTargetBounds = null;
+ cancelScheduledPlacement();
+ }
+
+ private void cancelScheduledPlacement() {
+ mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable);
+ mPendingPlacement = null;
+
+ if (mUnstashRunnable != null) {
+ mMainHandler.removeCallbacks(mUnstashRunnable);
+ mUnstashRunnable = null;
+ }
+ }
+
+ private void applyPlacementBounds(Rect bounds, int animationDuration) {
+ if (bounds == null) {
+ return;
+ }
+
+ mCurrentPlacementBounds = bounds;
+ Rect adjustedBounds = mTvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(bounds);
+ movePipTo(adjustedBounds, animationDuration);
+ }
+
+ /** Animates the PiP to the given bounds with the given animation duration. */
+ private void movePipTo(Rect bounds, int animationDuration) {
+ if (Objects.equals(mPipTargetBounds, bounds)) {
+ return;
+ }
+
+ mPipTargetBounds = bounds;
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString());
+ }
+
+ if (mListener != null) {
+ mListener.onPipTargetBoundsChange(bounds, animationDuration);
+ }
+ }
+
+ /**
+ * Interface being notified of changes to the PiP bounds as calculated by
+ * @link TvPipBoundsController}.
+ */
+ public interface PipBoundsListener {
+ /**
+ * Called when the calculated PiP bounds are changing.
+ *
+ * @param newTargetBounds The new bounds of the PiP.
+ * @param animationDuration The animation duration for the PiP movement.
+ */
+ void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index fcd1f95..fa48def 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -25,12 +25,10 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.TaskInfo;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.RemoteException;
import android.view.Gravity;
@@ -46,25 +44,24 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
- TvPipMenuController.Delegate, TvPipNotificationController.Delegate,
- DisplayController.OnDisplaysChangedListener {
+ TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
+ TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener {
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
@@ -98,19 +95,18 @@
private final TvPipBoundsState mTvPipBoundsState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+ private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final ShellExecutor mMainExecutor;
- private final Handler mMainHandler;
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
- private Runnable mUnstashRunnable;
private RemoteAction mCloseAction;
// How long the shell will wait for the app to close the PiP if a custom action is set.
@@ -123,6 +119,7 @@
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -133,12 +130,12 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor,
- Handler mainHandler) {
+ ShellExecutor mainExecutor) {
return new TvPipController(
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ tvPipBoundsController,
pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
@@ -149,14 +146,14 @@
pipParamsChangedForwarder,
displayController,
wmShell,
- mainExecutor,
- mainHandler).mImpl;
+ mainExecutor).mImpl;
}
private TvPipController(
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -167,16 +164,16 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor,
- Handler mainHandler) {
+ ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
- mMainHandler = mainHandler;
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
+ mTvPipBoundsController = tvPipBoundsController;
+ mTvPipBoundsController.setListener(this);
mPipMediaController = pipMediaController;
@@ -227,7 +224,7 @@
/**
* Starts the process if bringing up the Pip menu if by issuing a command to move Pip
* task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
- * task/window is properly positioned in {@link #onPipTransitionFinished(ComponentName, int)}.
+ * task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
*/
@Override
public void showPictureInPictureMenu() {
@@ -256,7 +253,6 @@
"%s: closeMenu(), state before=%s", TAG, stateToName(mState));
}
setState(STATE_PIP);
- mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas();
updatePinnedStackBounds();
}
@@ -293,6 +289,8 @@
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
+ mPipNotificationController.updateExpansionState();
+
updatePinnedStackBounds();
}
@@ -329,68 +327,35 @@
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mTvPipBoundsState.getDisplayId() == displayId) {
+ boolean unrestrictedAreasChanged = !Objects.equals(unrestricted,
+ mTvPipBoundsState.getUnrestrictedKeepClearAreas());
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- updatePinnedStackBounds();
+ updatePinnedStackBounds(mResizeAnimationDuration, unrestrictedAreasChanged);
}
}
private void updatePinnedStackBounds() {
- updatePinnedStackBounds(mResizeAnimationDuration);
+ updatePinnedStackBounds(mResizeAnimationDuration, true);
}
/**
* Update the PiP bounds based on the state of the PiP and keep clear areas.
- * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
*/
- private void updatePinnedStackBounds(int animationDuration) {
+ private void updatePinnedStackBounds(int animationDuration, boolean immediate) {
if (mState == STATE_NO_PIP) {
return;
}
-
final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode();
final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition;
- final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds();
-
- int stashType =
- disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType();
- mTvPipBoundsState.setStashed(stashType);
-
- if (stayAtAnchorPosition) {
- movePinnedStackTo(placement.getAnchorBounds());
- } else if (disallowStashing) {
- movePinnedStackTo(placement.getUnstashedBounds());
- } else {
- movePinnedStackTo(placement.getBounds());
- }
-
- if (mUnstashRunnable != null) {
- mMainHandler.removeCallbacks(mUnstashRunnable);
- mUnstashRunnable = null;
- }
- if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
- mUnstashRunnable = () -> {
- movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
- };
- mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
- }
+ mTvPipBoundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing,
+ animationDuration, immediate);
}
- /** Animates the PiP to the given bounds. */
- private void movePinnedStackTo(Rect bounds) {
- movePinnedStackTo(bounds, mResizeAnimationDuration);
- }
-
- /** Animates the PiP to the given bounds with the given animation duration. */
- private void movePinnedStackTo(Rect bounds, int animationDuration) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
- }
- mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
- animationDuration, rect -> {
- mTvPipMenuController.updateExpansionState();
- });
- mTvPipMenuController.onPipTransitionStarted(bounds);
+ @Override
+ public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) {
+ mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds,
+ animationDuration, rect -> mTvPipMenuController.updateExpansionState());
+ mTvPipMenuController.onPipTransitionStarted(newTargetBounds);
}
/**
@@ -429,7 +394,7 @@
@Override
public void closeEduText() {
- updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
+ updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false);
}
private void registerSessionListenerForCurrentUser() {
@@ -471,6 +436,7 @@
mPipNotificationController.dismiss();
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
+ mTvPipBoundsController.onPipDismissed();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 07dccd5..1e54436 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -33,17 +33,14 @@
import kotlin.math.roundToInt
private const val DEFAULT_PIP_MARGINS = 48
-private const val DEFAULT_STASH_DURATION = 5000L
private const val RELAX_DEPTH = 1
private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15
/**
* This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking
* into account app defined keep clear areas.
- *
- * @param clock A function returning a current timestamp (in milliseconds)
*/
-class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
+class TvPipKeepClearAlgorithm() {
/**
* Result of the positioning algorithm.
*
@@ -51,17 +48,17 @@
* @param anchorBounds The bounds of the PiP anchor position
* (where the PiP would be placed if there were no keep clear areas)
* @param stashType Where the PiP has been stashed, if at all
- * @param unstashDestinationBounds If stashed, the PiP should move to this position after
- * [stashDuration] has passed.
- * @param unstashTime If stashed, the time at which the PiP should move
- * to [unstashDestinationBounds]
+ * @param unstashDestinationBounds If stashed, the PiP should move to this position when
+ * unstashing.
+ * @param triggerStash Whether this placement should trigger the PiP to stash, or extend
+ * the unstash timeout if already stashed.
*/
data class Placement(
val bounds: Rect,
val anchorBounds: Rect,
@PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
val unstashDestinationBounds: Rect? = null,
- val unstashTime: Long = 0L
+ val triggerStash: Boolean = false
) {
/** Bounds to use if the PiP should not be stashed. */
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
@@ -79,12 +76,6 @@
/** The distance the PiP peeks into the screen when stashed */
var stashOffset = DEFAULT_PIP_MARGINS
- /**
- * How long (in milliseconds) the PiP should stay stashed for after the last time the
- * keep clear areas causing the PiP to stash have changed.
- */
- var stashDuration = DEFAULT_STASH_DURATION
-
/** The fraction of screen width/height restricted keep clear areas can move the PiP */
var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION
@@ -93,14 +84,10 @@
private var transformedMovementBounds = Rect()
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
- private var lastStashTime: Long = Long.MIN_VALUE
/** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
* decorations are relevant for calculating intersecting keep clear areas */
private var pipPermanentDecorInsets = Insets.NONE
- /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
- * decorations are not relevant for calculating intersecting keep clear areas */
- private var pipTemporaryDecorInsets = Insets.NONE
/**
* Calculates the position the PiP should be placed at, taking into consideration the
@@ -113,8 +100,8 @@
* always try to respect these areas.
*
* If no free space the PiP is allowed to move to can be found, a stashed position is returned
- * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has
- * passed as [Placement.unstashDestinationBounds].
+ * as [Placement.bounds], along with a position to move to when the PiP unstashes
+ * as [Placement.unstashDestinationBounds].
*
* @param pipSize The size of the PiP window
* @param restrictedAreas The restricted keep clear areas
@@ -130,13 +117,11 @@
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
val pipSizeWithAllDecors = addDecors(pipSize)
- val pipAnchorBoundsWithAllDecors =
+ val pipAnchorBoundsWithDecors =
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
- val pipAnchorBoundsWithPermanentDecors =
- removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors)
val result = calculatePipPositionTransformed(
- pipAnchorBoundsWithPermanentDecors,
+ pipAnchorBoundsWithDecors,
transformedRestrictedAreas,
transformedUnrestrictedAreas
)
@@ -152,7 +137,7 @@
anchorBounds,
getStashType(pipBounds, unstashedDestBounds),
unstashedDestBounds,
- result.unstashTime
+ result.triggerStash
)
}
@@ -213,26 +198,13 @@
!lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition
- val now = clock()
- if (areasOverlappingUnstashPositionChanged) {
- lastStashTime = now
- }
-
- // If overlapping areas haven't changed and the stash duration has passed, we can
- // place the PiP at the unstash position
- val unstashTime = lastStashTime + stashDuration
- if (now >= unstashTime) {
- return Placement(unstashBounds, pipAnchorBounds)
- }
-
- // Otherwise, we'll stash it close to the unstash position
val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas)
return Placement(
stashedBounds,
pipAnchorBounds,
getStashType(stashedBounds, unstashBounds),
unstashBounds,
- unstashTime
+ areasOverlappingUnstashPositionChanged
)
}
@@ -439,14 +411,6 @@
}
/**
- * Prevents the PiP from being stashed for the current set of keep clear areas.
- * The PiP may stash again if keep clear areas change.
- */
- fun keepUnstashedForCurrentKeepClearAreas() {
- lastStashTime = Long.MIN_VALUE
- }
-
- /**
* Updates the size of the screen.
*
* @param size The new size of the screen
@@ -492,10 +456,6 @@
pipPermanentDecorInsets = insets
}
- fun setPipTemporaryDecorInsets(insets: Insets) {
- pipTemporaryDecorInsets = insets
- }
-
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -790,7 +750,6 @@
private fun addDecors(size: Size): Size {
val bounds = Rect(0, 0, size.width, size.height)
bounds.inset(pipPermanentDecorInsets)
- bounds.inset(pipTemporaryDecorInsets)
return Size(bounds.width(), bounds.height())
}
@@ -805,19 +764,6 @@
return bounds
}
- /**
- * Removes the space that was reserved for temporary decorations around the PiP
- * @param bounds the bounds (in base case) to remove the insets from
- */
- private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect {
- if (pipTemporaryDecorInsets == Insets.NONE) return bounds
-
- val reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
- val boundsInScreenSpace = fromTransformedSpace(bounds)
- boundsInScreenSpace.inset(reverseInsets)
- return toTransformedSpace(boundsInScreenSpace)
- }
-
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 132c044..4ce45e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -209,7 +209,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementMenuOnly()", TAG);
}
- mInMoveMode = true;
+ setInMoveMode(true);
mCloseAfterExitMoveMenu = true;
showMenuInternal();
}
@@ -219,7 +219,7 @@
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
}
- mInMoveMode = false;
+ setInMoveMode(false);
mCloseAfterExitMoveMenu = false;
showMenuInternal();
}
@@ -293,6 +293,17 @@
return mInMoveMode;
}
+ private void setInMoveMode(boolean moveMode) {
+ if (mInMoveMode == moveMode) {
+ return;
+ }
+
+ mInMoveMode = moveMode;
+ if (mDelegate != null) {
+ mDelegate.onInMoveModeChanged();
+ }
+ }
+
@Override
public void onEnterMoveMode() {
if (DEBUG) {
@@ -300,7 +311,7 @@
"%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
mCloseAfterExitMoveMenu);
}
- mInMoveMode = true;
+ setInMoveMode(true);
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
}
@@ -312,13 +323,13 @@
mCloseAfterExitMoveMenu);
}
if (mCloseAfterExitMoveMenu) {
- mInMoveMode = false;
+ setInMoveMode(false);
mCloseAfterExitMoveMenu = false;
closeMenu();
return true;
}
if (mInMoveMode) {
- mInMoveMode = false;
+ setInMoveMode(false);
mPipMenuView.showButtonsMenu();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 4033f03..61a609d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,36 +16,47 @@
package com.android.wm.shell.pip.tv;
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteAction;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.media.MediaMetadata;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
+import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ImageUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.Objects;
+import java.util.ArrayList;
+import java.util.List;
/**
- * A notification that informs users that PIP is running and also provides PIP controls.
- * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
- * configuration changes.
+ * A notification that informs users that PiP is running and also provides PiP controls.
+ * <p>Once it's created, it will manage the PiP notification UI by itself except for handling
+ * configuration changes and user initiated expanded PiP toggling.
*/
public class TvPipNotificationController {
private static final String TAG = "TvPipNotification";
- private static final boolean DEBUG = TvPipController.DEBUG;
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
@@ -60,6 +71,8 @@
"com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
private static final String ACTION_TOGGLE_EXPANDED_PIP =
"com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+ private static final String ACTION_FULLSCREEN =
+ "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
private final Context mContext;
private final PackageManager mPackageManager;
@@ -68,44 +81,88 @@
private final ActionBroadcastReceiver mActionBroadcastReceiver;
private final Handler mMainHandler;
private Delegate mDelegate;
+ private final TvPipBoundsState mTvPipBoundsState;
private String mDefaultTitle;
+ private final List<RemoteAction> mCustomActions = new ArrayList<>();
+ private final List<RemoteAction> mMediaActions = new ArrayList<>();
+ private RemoteAction mCustomCloseAction;
+
+ private MediaSession.Token mMediaSessionToken;
+
/** Package name for the application that owns PiP window. */
private String mPackageName;
- private boolean mNotified;
- private String mMediaTitle;
- private Bitmap mArt;
+
+ private boolean mIsNotificationShown;
+ private String mPipTitle;
+ private String mPipSubtitle;
+
+ private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
+ PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
Handler mainHandler) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
mMainHandler = mainHandler;
+ mTvPipBoundsState = tvPipBoundsState;
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
- .setOngoing(false)
+ .setOngoing(true)
.setCategory(Notification.CATEGORY_SYSTEM)
.setShowWhen(true)
.setSmallIcon(R.drawable.pip_icon)
+ .setAllowSystemGeneratedContextualActions(false)
+ .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
+ .setDeleteIntent(getCloseAction().actionIntent)
.extend(new Notification.TvExtender()
.setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
mActionBroadcastReceiver = new ActionBroadcastReceiver();
- pipMediaController.addMetadataListener(this::onMediaMetadataChanged);
+ pipMediaController.addActionListener(this::onMediaActionsChanged);
+ pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
+
+ pipParamsChangedForwarder.addListener(
+ new PipParamsChangedForwarder.PipParamsChangedCallback() {
+ @Override
+ public void onExpandedAspectRatioChanged(float ratio) {
+ updateExpansionState();
+ }
+
+ @Override
+ public void onActionsChanged(List<RemoteAction> actions,
+ RemoteAction closeAction) {
+ mCustomActions.clear();
+ mCustomActions.addAll(actions);
+ mCustomCloseAction = closeAction;
+ updateNotificationContent();
+ }
+
+ @Override
+ public void onTitleChanged(String title) {
+ mPipTitle = title;
+ updateNotificationContent();
+ }
+
+ @Override
+ public void onSubtitleChanged(String subtitle) {
+ mPipSubtitle = subtitle;
+ updateNotificationContent();
+ }
+ });
onConfigurationChanged(context);
}
void setDelegate(Delegate delegate) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setDelegate(), delegate=%s", TAG, delegate);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
+ TAG, delegate);
+
if (mDelegate != null) {
throw new IllegalStateException(
"The delegate has already been set and should not change.");
@@ -118,90 +175,181 @@
}
void show(String packageName) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
if (mDelegate == null) {
throw new IllegalStateException("Delegate is not set.");
}
+ mIsNotificationShown = true;
mPackageName = packageName;
- update();
+ mActivityIcon = getActivityIcon();
mActionBroadcastReceiver.register();
+
+ updateNotificationContent();
}
void dismiss() {
- mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
- mNotified = false;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG);
+
+ mIsNotificationShown = false;
mPackageName = null;
mActionBroadcastReceiver.unregister();
+
+ mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
}
- private void onMediaMetadataChanged(MediaMetadata metadata) {
- if (updateMediaControllerMetadata(metadata) && mNotified) {
- // update notification
- update();
+ private Notification.Action getToggleAction(boolean expanded) {
+ if (expanded) {
+ return createSystemAction(R.drawable.pip_ic_collapse,
+ R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
+ } else {
+ return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
+ ACTION_TOGGLE_EXPANDED_PIP);
}
}
+ private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ Icon.createWithResource(mContext, iconRes),
+ mContext.getString(titleRes),
+ createPendingIntent(mContext, action));
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+ private void onMediaActionsChanged(List<RemoteAction> actions) {
+ mMediaActions.clear();
+ mMediaActions.addAll(actions);
+ if (mCustomActions.isEmpty()) {
+ updateNotificationContent();
+ }
+ }
+
+ private void onMediaSessionTokenChanged(MediaSession.Token token) {
+ mMediaSessionToken = token;
+ updateNotificationContent();
+ }
+
+ private Notification.Action remoteToNotificationAction(RemoteAction action) {
+ return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
+ }
+
+ private Notification.Action remoteToNotificationAction(RemoteAction action,
+ int semanticAction) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
+ action.getTitle(),
+ action.getActionIntent());
+ if (action.getContentDescription() != null) {
+ Bundle extras = new Bundle();
+ extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+ action.getContentDescription());
+ builder.addExtras(extras);
+ }
+ builder.setSemanticAction(semanticAction);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+ private Notification.Action[] getNotificationActions() {
+ final List<Notification.Action> actions = new ArrayList<>();
+
+ // 1. Fullscreen
+ actions.add(getFullscreenAction());
+ // 2. Close
+ actions.add(getCloseAction());
+ // 3. App actions
+ final List<RemoteAction> appActions =
+ mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
+ for (RemoteAction appAction : appActions) {
+ if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
+ || !appAction.isEnabled()) {
+ continue;
+ }
+ actions.add(remoteToNotificationAction(appAction));
+ }
+ // 4. Move
+ actions.add(getMoveAction());
+ // 5. Toggle expansion (if expanded PiP enabled)
+ if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
+ && mTvPipBoundsState.isTvExpandedPipSupported()) {
+ actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
+ }
+ return actions.toArray(new Notification.Action[0]);
+ }
+
+ private Notification.Action getCloseAction() {
+ if (mCustomCloseAction == null) {
+ return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
+ ACTION_CLOSE_PIP);
+ } else {
+ return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
+ }
+ }
+
+ private Notification.Action getFullscreenAction() {
+ return createSystemAction(R.drawable.pip_ic_fullscreen_white,
+ R.string.pip_fullscreen, ACTION_FULLSCREEN);
+ }
+
+ private Notification.Action getMoveAction() {
+ return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
+ ACTION_MOVE_PIP);
+ }
+
/**
- * Called by {@link PipController} when the configuration is changed.
+ * Called by {@link TvPipController} when the configuration is changed.
*/
void onConfigurationChanged(Context context) {
mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
- if (mNotified) {
- // Update the notification.
- update();
- }
+ updateNotificationContent();
}
- private void update() {
- mNotified = true;
+ void updateExpansionState() {
+ updateNotificationContent();
+ }
+
+ private void updateNotificationContent() {
+ if (mPackageManager == null || !mIsNotificationShown) {
+ return;
+ }
+
+ Notification.Action[] actions = getNotificationActions();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
+ getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
+ for (Notification.Action action : actions) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
+ action.toString());
+ }
+
mNotificationBuilder
.setWhen(System.currentTimeMillis())
- .setContentTitle(getNotificationTitle());
- if (mArt != null) {
- mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
- .bigPicture(mArt));
- } else {
- mNotificationBuilder.setStyle(null);
- }
+ .setContentTitle(getNotificationTitle())
+ .setContentText(mPipSubtitle)
+ .setSubText(getApplicationLabel(mPackageName))
+ .setActions(actions);
+ setPipIcon();
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+ mNotificationBuilder.setExtras(extras);
+
+ // TvExtender not recognized if not set last.
+ mNotificationBuilder.extend(new Notification.TvExtender()
+ .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
+ .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
mNotificationBuilder.build());
}
- private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
- String title = null;
- Bitmap art = null;
- if (metadata != null) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
- if (TextUtils.isEmpty(title)) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- }
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- if (art == null) {
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- }
- }
-
- if (TextUtils.equals(title, mMediaTitle) && Objects.equals(art, mArt)) {
- return false;
- }
-
- mMediaTitle = title;
- mArt = art;
-
- return true;
- }
-
-
private String getNotificationTitle() {
- if (!TextUtils.isEmpty(mMediaTitle)) {
- return mMediaTitle;
+ if (!TextUtils.isEmpty(mPipTitle)) {
+ return mPipTitle;
}
-
final String applicationTitle = getApplicationLabel(mPackageName);
if (!TextUtils.isEmpty(applicationTitle)) {
return applicationTitle;
}
-
return mDefaultTitle;
}
@@ -214,10 +362,37 @@
}
}
+ private void setPipIcon() {
+ if (mActivityIcon != null) {
+ mNotificationBuilder.setLargeIcon(mActivityIcon);
+ return;
+ }
+ // Fallback: Picture-in-Picture icon
+ mNotificationBuilder.setLargeIcon(Icon.createWithResource(mContext, R.drawable.pip_icon));
+ }
+
+ private Bitmap getActivityIcon() {
+ if (mContext == null) return null;
+ ComponentName componentName = PipUtils.getTopPipActivity(mContext).first;
+ if (componentName == null) return null;
+
+ Drawable drawable;
+ try {
+ drawable = mPackageManager.getActivityIcon(componentName);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ int width = mContext.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width);
+ int height = mContext.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height);
+ return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
+ }
+
private static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private class ActionBroadcastReceiver extends BroadcastReceiver {
@@ -228,6 +403,7 @@
mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
mIntentFilter.addAction(ACTION_MOVE_PIP);
mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+ mIntentFilter.addAction(ACTION_FULLSCREEN);
}
boolean mRegistered = false;
@@ -249,10 +425,8 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: on(Broadcast)Receive(), action=%s", TAG, action);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
if (ACTION_SHOW_PIP_MENU.equals(action)) {
mDelegate.showPictureInPictureMenu();
@@ -262,14 +436,21 @@
mDelegate.enterPipMovementMenu();
} else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
mDelegate.togglePipExpansion();
+ } else if (ACTION_FULLSCREEN.equals(action)) {
+ mDelegate.movePipToFullscreen();
}
}
}
interface Delegate {
void showPictureInPictureMenu();
+
void closePip();
+
void enterPipMovementMenu();
+
void togglePipExpansion();
+
+ void movePipToFullscreen();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 64017e1..d04c349 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -40,6 +40,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG,
false, Consts.TAG_WM_SHELL),
+ WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 91f9d25..63774fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -54,6 +54,9 @@
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -68,6 +71,7 @@
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -147,7 +151,9 @@
private final int mDisplayId;
private SplitLayout mSplitLayout;
+ private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
+ private boolean mKeyguardShowing;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -404,6 +410,7 @@
mSplitLayout.init();
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
+ mIsDividerRemoteAnimating = true;
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
@@ -417,7 +424,6 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
final IRemoteAnimationFinishedCallback finishedCallback) {
- mIsDividerRemoteAnimating = true;
RemoteAnimationTarget[] augmentedNonApps =
new RemoteAnimationTarget[nonApps.length + 1];
for (int i = 0; i < nonApps.length; ++i) {
@@ -494,8 +500,10 @@
}
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
}
private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
@@ -510,10 +518,6 @@
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
} else {
mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- });
}
}
@@ -623,16 +627,12 @@
}
void onKeyguardVisibilityChanged(boolean showing) {
+ mKeyguardShowing = showing;
if (!mMainStage.isActive()) {
return;
}
- if (ENABLE_SHELL_TRANSITIONS) {
- // Update divider visibility so it won't float on top of keyguard.
- setDividerVisibility(!showing, null /* transaction */);
- }
-
- if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
if (ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
@@ -643,7 +643,10 @@
mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
EXIT_REASON_DEVICE_FOLDED);
}
+ return;
}
+
+ setDividerVisibility(!mKeyguardShowing, null);
}
void onFinishedWakingUp() {
@@ -727,6 +730,7 @@
setResizingSplits(false /* resizing */);
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
+ setDividerVisibility(false, t);
});
// Hide divider and reset its position.
@@ -976,6 +980,9 @@
updateUnfoldBounds();
return;
}
+ // Clear the divider remote animating flag as the divider will be re-rendered to apply
+ // the new rotation config.
+ mIsDividerRemoteAnimating = false;
mSplitLayout.update(null /* t */);
onLayoutSizeChanged(mSplitLayout);
}
@@ -1055,8 +1062,31 @@
}
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
+ if (visible == mDividerVisible) {
+ return;
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Request to %s divider bar from %s.", TAG,
+ (visible ? "show" : "hide"), Debug.getCaller());
+
+ // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
+ // dismissing animation.
+ if (visible && mKeyguardShowing) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Defer showing divider bar due to keyguard showing.", TAG);
+ return;
+ }
+
mDividerVisible = visible;
sendSplitVisibilityChanged();
+
+ if (mIsDividerRemoteAnimating) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ return;
+ }
+
if (t != null) {
applyDividerVisibility(t);
} else {
@@ -1066,15 +1096,56 @@
private void applyDividerVisibility(SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (mIsDividerRemoteAnimating || dividerLeash == null) return;
+ if (dividerLeash == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to divider leash not ready.", TAG);
+ return;
+ }
+ if (mIsDividerRemoteAnimating) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ return;
+ }
+
+ if (mDividerFadeInAnimator != null && mDividerFadeInAnimator.isRunning()) {
+ mDividerFadeInAnimator.cancel();
+ }
if (mDividerVisible) {
- t.show(dividerLeash);
- t.setAlpha(dividerLeash, 1);
- t.setLayer(dividerLeash, Integer.MAX_VALUE);
- t.setPosition(dividerLeash,
- mSplitLayout.getRefDividerBounds().left,
- mSplitLayout.getRefDividerBounds().top);
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mDividerFadeInAnimator.addUpdateListener(animation -> {
+ if (dividerLeash == null) {
+ mDividerFadeInAnimator.cancel();
+ return;
+ }
+ transaction.setAlpha(dividerLeash, (float) animation.getAnimatedValue());
+ transaction.apply();
+ });
+ mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (dividerLeash == null) {
+ mDividerFadeInAnimator.cancel();
+ return;
+ }
+ transaction.show(dividerLeash);
+ transaction.setAlpha(dividerLeash, 0);
+ transaction.setLayer(dividerLeash, Integer.MAX_VALUE);
+ transaction.setPosition(dividerLeash,
+ mSplitLayout.getRefDividerBounds().left,
+ mSplitLayout.getRefDividerBounds().top);
+ transaction.apply();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransactionPool.release(transaction);
+ mDividerFadeInAnimator = null;
+ }
+ });
+
+ mDividerFadeInAnimator.start();
} else {
t.hide(dividerLeash);
}
@@ -1096,10 +1167,8 @@
mSplitLayout.init();
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- setDividerVisibility(true, t);
- });
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index d89ddd2..8cee4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -35,6 +35,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -53,6 +55,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
@@ -68,7 +71,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -102,7 +104,7 @@
*/
private static final float NO_BACKGROUND_SCALE = 192f / 160;
private final Context mContext;
- private final IconProvider mIconProvider;
+ private final HighResIconProvider mHighResIconProvider;
private int mIconSize;
private int mDefaultIconSize;
@@ -115,12 +117,10 @@
private final Handler mSplashscreenWorkerHandler;
@VisibleForTesting
final ColorCache mColorCache;
- private final ShellExecutor mSplashScreenExecutor;
- SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool,
- ShellExecutor splashScreenExecutor) {
+ SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
mContext = context;
- mIconProvider = iconProvider;
+ mHighResIconProvider = new HighResIconProvider(mContext, iconProvider);
mTransactionPool = pool;
// Initialize Splashscreen worker thread
@@ -131,7 +131,6 @@
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
- mSplashScreenExecutor = splashScreenExecutor;
}
/**
@@ -416,18 +415,16 @@
|| mTmpAttrs.mIconBgColor == mThemeColor) {
mFinalIconSize *= NO_BACKGROUND_SCALE;
}
- createIconDrawable(iconDrawable, false);
+ createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
} else {
final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
final int scaledIconDpi =
(int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
- iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi);
+ iconDrawable = mHighResIconProvider.getIcon(
+ mActivityInfo, densityDpi, scaledIconDpi);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (iconDrawable == null) {
- iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
- }
if (!processAdaptiveIcon(iconDrawable)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"The icon is not an AdaptiveIconDrawable");
@@ -437,7 +434,8 @@
scaledIconDpi, mFinalIconSize);
final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- createIconDrawable(new BitmapDrawable(bitmap), true);
+ createIconDrawable(new BitmapDrawable(bitmap), true,
+ mHighResIconProvider.mLoadInDetail);
}
}
@@ -450,14 +448,16 @@
}
}
- private void createIconDrawable(Drawable iconDrawable, boolean legacy) {
+ private void createIconDrawable(Drawable iconDrawable, boolean legacy,
+ boolean loadInDetail) {
if (legacy) {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeLegacyIconDrawable(
- iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
+ iconDrawable, mDefaultIconSize, mFinalIconSize, loadInDetail,
+ mSplashscreenWorkerHandler);
} else {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
- mFinalIconSize, mSplashscreenWorkerHandler);
+ mFinalIconSize, loadInDetail, mSplashscreenWorkerHandler);
}
}
@@ -506,11 +506,11 @@
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
- createIconDrawable(iconForeground, false);
+ createIconDrawable(iconForeground, false, mHighResIconProvider.mLoadInDetail);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"processAdaptiveIcon: draw whole icon");
- createIconDrawable(iconDrawable, false);
+ createIconDrawable(iconDrawable, false, mHighResIconProvider.mLoadInDetail);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
@@ -1015,4 +1015,77 @@
playAnimation.run();
}
}
+
+ /**
+ * When loading a BitmapDrawable object with specific density, there will decode the image based
+ * on the density from display metrics, so even when load with higher override density, the
+ * final intrinsic size of a BitmapDrawable can still not big enough to draw on expect size.
+ *
+ * So here we use a standalone IconProvider object to load the Drawable object for higher
+ * density, and the resources object won't affect the entire system.
+ *
+ */
+ private static class HighResIconProvider {
+ private final Context mSharedContext;
+ private final IconProvider mSharedIconProvider;
+ private boolean mLoadInDetail;
+
+ // only create standalone icon provider when the density dpi is low.
+ private Context mStandaloneContext;
+ private IconProvider mStandaloneIconProvider;
+
+ HighResIconProvider(Context context, IconProvider sharedIconProvider) {
+ mSharedContext = context;
+ mSharedIconProvider = sharedIconProvider;
+ }
+
+ Drawable getIcon(ActivityInfo activityInfo, int currentDpi, int iconDpi) {
+ mLoadInDetail = false;
+ Drawable drawable;
+ if (currentDpi < iconDpi && currentDpi < DisplayMetrics.DENSITY_XHIGH) {
+ drawable = loadFromStandalone(activityInfo, currentDpi, iconDpi);
+ } else {
+ drawable = mSharedIconProvider.getIcon(activityInfo, iconDpi);
+ }
+
+ if (drawable == null) {
+ drawable = mSharedContext.getPackageManager().getDefaultActivityIcon();
+ }
+ return drawable;
+ }
+
+ private Drawable loadFromStandalone(ActivityInfo activityInfo, int currentDpi,
+ int iconDpi) {
+ if (mStandaloneContext == null) {
+ final Configuration defConfig = mSharedContext.getResources().getConfiguration();
+ mStandaloneContext = mSharedContext.createConfigurationContext(defConfig);
+ mStandaloneIconProvider = new IconProvider(mStandaloneContext);
+ }
+ Resources resources;
+ try {
+ resources = mStandaloneContext.getPackageManager()
+ .getResourcesForApplication(activityInfo.applicationInfo);
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) {
+ resources = null;
+ }
+ if (resources != null) {
+ updateResourcesDpi(resources, iconDpi);
+ }
+ final Drawable drawable = mStandaloneIconProvider.getIcon(activityInfo, iconDpi);
+ mLoadInDetail = true;
+ // reset density dpi
+ if (resources != null) {
+ updateResourcesDpi(resources, currentDpi);
+ }
+ return drawable;
+ }
+
+ private void updateResourcesDpi(Resources resources, int densityDpi) {
+ final Configuration config = resources.getConfiguration();
+ final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ config.densityDpi = densityDpi;
+ displayMetrics.densityDpi = densityDpi;
+ resources.updateConfiguration(config, displayMetrics);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 5f52071..7f6bfd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -62,7 +62,7 @@
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- Handler splashscreenWorkerHandler) {
+ boolean loadInDetail, Handler splashscreenWorkerHandler) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
@@ -74,13 +74,13 @@
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
foreground = new ImmobileIconDrawable(foregroundDrawable,
- srcIconSize, iconSize, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
} else {
// Adaptive icon don't handle transparency so we draw the background of the adaptive
// icon with the same color as the window background color instead of using two layers
foreground = new ImmobileIconDrawable(
new AdaptiveForegroundDrawable(foregroundDrawable),
- srcIconSize, iconSize, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
}
if (drawBackground) {
@@ -91,9 +91,9 @@
}
static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
- int iconSize, Handler splashscreenWorkerHandler) {
+ int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) {
return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
- splashscreenWorkerHandler)};
+ loadInDetail, splashscreenWorkerHandler)};
}
/**
@@ -106,11 +106,16 @@
private final Matrix mMatrix = new Matrix();
private Bitmap mIconBitmap;
- ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize,
+ ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
Handler splashscreenWorkerHandler) {
- final float scale = (float) iconSize / srcIconSize;
- mMatrix.setScale(scale, scale);
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+ // This icon has lower density, don't scale it.
+ if (loadInDetail) {
+ splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize));
+ } else {
+ final float scale = (float) iconSize / srcIconSize;
+ mMatrix.setScale(scale, scale);
+ splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+ }
}
private void preDrawIcon(Drawable drawable, int size) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 464ab1a..54d62ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -153,8 +153,7 @@
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
- mSplashScreenExecutor);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index cf4ea46..41cd31a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -37,7 +37,7 @@
val displayBounds = WindowUtils.displayBounds
val secondaryAppBounds = Region.from(0,
dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
return secondaryAppBounds
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index a510d69..e2da1a4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -171,7 +171,7 @@
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
@@ -192,7 +192,7 @@
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index a899709..ea10be5 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -37,6 +37,7 @@
"androidx.test.ext.junit",
"androidx.dynamicanimation_dynamicanimation",
"dagger2",
+ "frameworks-base-testutils",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"mockito-target-extended-minus-junit4",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6cf8829..42b1014 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -209,12 +209,11 @@
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
- // Check that back start is dispatched.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verify(mIOnBackInvokedCallback).onBackStarted();
- // Check that back progress is dispatched.
+ // Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ verify(mIOnBackInvokedCallback).onBackStarted();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
new file mode 100644
index 0000000..05e4722
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.wm.shell.pip.tv
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.os.Handler
+import android.os.test.TestLooper
+import android.testing.AndroidTestingRunner
+
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.AdditionalAnswers.returnsFirstArg
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+class TvPipBoundsControllerTest {
+ val ANIMATION_DURATION = 100
+ val STASH_DURATION = 5000
+ val FAR_FUTURE = 60 * 60000L
+ val ANCHOR_BOUNDS = Rect(90, 90, 100, 100)
+ val STASHED_BOUNDS = Rect(99, 90, 109, 100)
+ val MOVED_BOUNDS = Rect(90, 80, 100, 90)
+ val STASHED_MOVED_BOUNDS = Rect(99, 80, 109, 90)
+ val ANCHOR_PLACEMENT = Placement(ANCHOR_BOUNDS, ANCHOR_BOUNDS)
+ val STASHED_PLACEMENT = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS,
+ STASH_TYPE_RIGHT, ANCHOR_BOUNDS, false)
+ val STASHED_PLACEMENT_RESTASH = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS,
+ STASH_TYPE_RIGHT, ANCHOR_BOUNDS, true)
+ val MOVED_PLACEMENT = Placement(MOVED_BOUNDS, ANCHOR_BOUNDS)
+ val STASHED_MOVED_PLACEMENT = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS,
+ STASH_TYPE_RIGHT, MOVED_BOUNDS, false)
+ val STASHED_MOVED_PLACEMENT_RESTASH = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS,
+ STASH_TYPE_RIGHT, MOVED_BOUNDS, true)
+
+ lateinit var boundsController: TvPipBoundsController
+ var time = 0L
+ lateinit var testLooper: TestLooper
+ lateinit var mainHandler: Handler
+
+ var inMenu = false
+ var inMoveMode = false
+
+ @Mock
+ lateinit var context: Context
+ @Mock
+ lateinit var resources: Resources
+ @Mock
+ lateinit var tvPipBoundsState: TvPipBoundsState
+ @Mock
+ lateinit var tvPipBoundsAlgorithm: TvPipBoundsAlgorithm
+ @Mock
+ lateinit var listener: TvPipBoundsController.PipBoundsListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ time = 0L
+ inMenu = false
+ inMoveMode = false
+
+ testLooper = TestLooper { time }
+ mainHandler = Handler(testLooper.getLooper())
+
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION)
+ whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any()))
+ .then(returnsFirstArg<Rect>())
+
+ boundsController = TvPipBoundsController(
+ context,
+ { time },
+ mainHandler,
+ tvPipBoundsState,
+ tvPipBoundsAlgorithm)
+ boundsController.setListener(listener)
+ }
+
+ @Test
+ fun testPlacement_MovedAfterDebounceTimeout() {
+ triggerPlacement(MOVED_PLACEMENT)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ @Test
+ fun testStashedPlacement_MovedAfterDebounceTimeout_Unstashes() {
+ triggerPlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS)
+ }
+
+ @Test
+ fun testDebounceSamePlacement_MovesDebounceTimeoutAfterFirstPlacement() {
+ triggerPlacement(MOVED_PLACEMENT)
+ advanceTimeTo(POSITION_DEBOUNCE_TIMEOUT_MILLIS / 2)
+ triggerPlacement(MOVED_PLACEMENT)
+
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS)
+ }
+
+ @Test
+ fun testNoMovementUntilPlacementStabilizes() {
+ triggerPlacement(ANCHOR_PLACEMENT)
+ advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10)
+ triggerPlacement(MOVED_PLACEMENT)
+ advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10)
+ triggerPlacement(ANCHOR_PLACEMENT)
+ advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10)
+ triggerPlacement(MOVED_PLACEMENT)
+
+ assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS)
+ }
+
+ @Test
+ fun testUnstashIfStashNoLongerNecessary() {
+ triggerPlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
+
+ triggerPlacement(ANCHOR_PLACEMENT)
+ assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, ANCHOR_BOUNDS)
+ }
+
+ @Test
+ fun testRestashingPlacementDelaysUnstash() {
+ triggerPlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
+
+ assertNoMovementUpTo(time + STASH_DURATION / 2)
+ triggerPlacement(STASHED_PLACEMENT_RESTASH)
+ assertNoMovementUpTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS)
+ assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS)
+ }
+
+ @Test
+ fun testNonRestashingPlacementDoesNotDelayUnstash() {
+ triggerPlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
+
+ assertNoMovementUpTo(time + STASH_DURATION / 2)
+ triggerPlacement(STASHED_PLACEMENT)
+ assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS)
+ }
+
+ @Test
+ fun testImmediatePlacement() {
+ triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovement(STASHED_BOUNDS)
+ assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS)
+ }
+
+ @Test
+ fun testInMoveMode_KeepAtAnchor() {
+ startMoveMode()
+ triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
+ assertMovement(ANCHOR_BOUNDS)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ @Test
+ fun testInMenu_Unstashed() {
+ openPipMenu()
+ triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
+ assertMovement(MOVED_BOUNDS)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ @Test
+ fun testCloseMenu_DoNotRestash() {
+ openPipMenu()
+ triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
+ assertMovement(MOVED_BOUNDS)
+
+ closePipMenu()
+ triggerPlacement(STASHED_MOVED_PLACEMENT)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ fun assertMovement(bounds: Rect) {
+ verify(listener).onPipTargetBoundsChange(eq(bounds), anyInt())
+ reset(listener)
+ }
+
+ fun assertMovementAt(timeMs: Long, bounds: Rect) {
+ assertNoMovementUpTo(timeMs - 1)
+ advanceTimeTo(timeMs)
+ assertMovement(bounds)
+ }
+
+ fun assertNoMovementUpTo(timeMs: Long) {
+ advanceTimeTo(timeMs)
+ verify(listener, never()).onPipTargetBoundsChange(any(), anyInt())
+ }
+
+ fun triggerPlacement(placement: Placement, immediate: Boolean = false) {
+ whenever(tvPipBoundsAlgorithm.getTvPipPlacement()).thenReturn(placement)
+ val stayAtAnchorPosition = inMoveMode
+ val disallowStashing = inMenu || stayAtAnchorPosition
+ boundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing,
+ ANIMATION_DURATION, immediate)
+ }
+
+ fun triggerImmediatePlacement(placement: Placement) {
+ triggerPlacement(placement, true)
+ }
+
+ fun openPipMenu() {
+ inMenu = true
+ inMoveMode = false
+ }
+
+ fun closePipMenu() {
+ inMenu = false
+ inMoveMode = false
+ }
+
+ fun startMoveMode() {
+ inMenu = true
+ inMoveMode = true
+ }
+
+ fun advanceTimeTo(ms: Long) {
+ time = ms
+ testLooper.dispatchAll()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index 46f388d..0fcc5cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -30,7 +30,9 @@
import org.junit.Before
import org.junit.Test
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
@RunWith(AndroidTestingRunner::class)
class TvPipKeepClearAlgorithmTest {
@@ -46,7 +48,6 @@
private lateinit var pipSize: Size
private lateinit var movementBounds: Rect
private lateinit var algorithm: TvPipKeepClearAlgorithm
- private var currentTime = 0L
private var restrictedAreas = mutableSetOf<Rect>()
private var unrestrictedAreas = mutableSetOf<Rect>()
private var gravity: Int = 0
@@ -58,16 +59,14 @@
restrictedAreas.clear()
unrestrictedAreas.clear()
- currentTime = 0L
pipSize = DEFAULT_PIP_SIZE
gravity = Gravity.BOTTOM or Gravity.RIGHT
- algorithm = TvPipKeepClearAlgorithm({ currentTime })
+ algorithm = TvPipKeepClearAlgorithm()
algorithm.setScreenSize(SCREEN_SIZE)
algorithm.setMovementBounds(movementBounds)
algorithm.pipAreaPadding = PADDING
algorithm.stashOffset = STASH_OFFSET
- algorithm.stashDuration = 5000L
algorithm.setGravity(gravity)
algorithm.maxRestrictedDistanceFraction = 0.3
}
@@ -265,7 +264,7 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_BOTTOM, placement.stashType)
assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
- assertEquals(algorithm.stashDuration, placement.unstashTime)
+ assertTrue(placement.triggerStash)
}
@Test
@@ -305,7 +304,7 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
- assertEquals(algorithm.stashDuration, placement.unstashTime)
+ assertTrue(placement.triggerStash)
}
@Test
@@ -352,9 +351,7 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
- assertEquals(algorithm.stashDuration, placement.unstashTime)
-
- currentTime += 1000
+ assertTrue(placement.triggerStash)
restrictedAreas.remove(sideBar)
placement = getActualPlacement()
@@ -363,7 +360,7 @@
}
@Test
- fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() {
+ fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() {
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -384,13 +381,13 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
- assertEquals(algorithm.stashDuration, placement.unstashTime)
-
- currentTime += algorithm.stashDuration
+ assertTrue(placement.triggerStash)
placement = getActualPlacement()
- assertEquals(expectedUnstashBounds, placement.bounds)
- assertNotStashed(placement)
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertFalse(placement.triggerStash)
}
@Test
@@ -415,9 +412,7 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
- assertEquals(algorithm.stashDuration, placement.unstashTime)
-
- currentTime += 1000
+ assertTrue(placement.triggerStash)
val newObstruction = Rect(
0,
@@ -431,7 +426,7 @@
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
- assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
+ assertTrue(placement.triggerStash)
}
@Test
@@ -458,21 +453,9 @@
@Test
fun test_PipInsets() {
- val permInsets = Insets.of(-1, -2, -3, -4)
- algorithm.setPipPermanentDecorInsets(permInsets)
- testInsetsForAllPositions(permInsets)
+ val insets = Insets.of(-1, -2, -3, -4)
+ algorithm.setPipPermanentDecorInsets(insets)
- val tempInsets = Insets.of(-4, -3, -2, -1)
- algorithm.setPipPermanentDecorInsets(Insets.NONE)
- algorithm.setPipTemporaryDecorInsets(tempInsets)
- testInsetsForAllPositions(tempInsets)
-
- algorithm.setPipPermanentDecorInsets(permInsets)
- algorithm.setPipTemporaryDecorInsets(tempInsets)
- testInsetsForAllPositions(Insets.add(permInsets, tempInsets))
- }
-
- private fun testInsetsForAllPositions(insets: Insets) {
gravity = Gravity.BOTTOM or Gravity.RIGHT
testAnchorPositionWithInsets(insets)
@@ -546,6 +529,6 @@
private fun assertNotStashed(actual: Placement) {
assertEquals(STASH_TYPE_NONE, actual.stashType)
assertNull(actual.unstashDestinationBounds)
- assertEquals(0L, actual.unstashTime)
+ assertFalse(actual.triggerStash)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index a55f737..ffaab65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -139,6 +139,7 @@
}
@Test
+ @UiThreadTest
public void testLaunchToSide() {
ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
@@ -173,6 +174,7 @@
}
@Test
+ @UiThreadTest
public void testLaunchPair() {
TransitionInfo info = createEnterPairInfo();
@@ -195,6 +197,7 @@
}
@Test
+ @UiThreadTest
public void testMonitorInSplit() {
enterSplit();
@@ -251,6 +254,7 @@
}
@Test
+ @UiThreadTest
public void testEnterRecents() {
enterSplit();
@@ -288,6 +292,7 @@
}
@Test
+ @UiThreadTest
public void testDismissFromBeingOccluded() {
enterSplit();
@@ -325,6 +330,7 @@
}
@Test
+ @UiThreadTest
public void testDismissFromMultiWindowSupport() {
enterSplit();
@@ -346,6 +352,7 @@
}
@Test
+ @UiThreadTest
public void testDismissSnap() {
enterSplit();
@@ -370,6 +377,7 @@
}
@Test
+ @UiThreadTest
public void testDismissFromAppFinish() {
enterSplit();
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 4826d5a..0780414 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -31,7 +31,8 @@
animator->detach();
}
-AnimatorManager::AnimatorManager(RenderNode& parent) : mParent(parent), mAnimationHandle(nullptr) {}
+AnimatorManager::AnimatorManager(RenderNode& parent)
+ : mParent(parent), mAnimationHandle(nullptr), mCancelAllAnimators(false) {}
AnimatorManager::~AnimatorManager() {
for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
@@ -82,8 +83,16 @@
}
mNewAnimators.clear();
}
- for (auto& animator : mAnimators) {
- animator->pushStaging(mAnimationHandle->context());
+
+ if (mCancelAllAnimators) {
+ for (auto& animator : mAnimators) {
+ animator->forceEndNow(mAnimationHandle->context());
+ }
+ mCancelAllAnimators = false;
+ } else {
+ for (auto& animator : mAnimators) {
+ animator->pushStaging(mAnimationHandle->context());
+ }
}
}
@@ -184,5 +193,9 @@
mAnimationHandle->release();
}
+void AnimatorManager::forceEndAnimators() {
+ mCancelAllAnimators = true;
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index a0df01d..6002661d 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -16,11 +16,11 @@
#ifndef ANIMATORMANAGER_H
#define ANIMATORMANAGER_H
-#include <vector>
-
#include <cutils/compiler.h>
#include <utils/StrongPointer.h>
+#include <vector>
+
#include "utils/Macros.h"
namespace android {
@@ -56,6 +56,8 @@
// Hard-ends all animators. May only be called on the UI thread.
void endAllStagingAnimators();
+ void forceEndAnimators();
+
// Hard-ends all animators that have been pushed. Used for cleanup if
// the ActivityContext is being destroyed
void endAllActiveAnimators();
@@ -71,6 +73,8 @@
// To improve the efficiency of resizing & removing from the vector
std::vector<sp<BaseRenderNodeAnimator> > mNewAnimators;
std::vector<sp<BaseRenderNodeAnimator> > mAnimators;
+
+ bool mCancelAllAnimators;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 944393c..db76390 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -543,6 +543,12 @@
renderNode->animators().endAllStagingAnimators();
}
+static void android_view_RenderNode_forceEndAnimators(JNIEnv* env, jobject clazz,
+ jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->animators().forceEndAnimators();
+}
+
// ----------------------------------------------------------------------------
// SurfaceView position callback
// ----------------------------------------------------------------------------
@@ -745,6 +751,7 @@
{"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize},
{"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator},
{"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators},
+ {"nForceEndAnimators", "(J)V", (void*)android_view_RenderNode_forceEndAnimators},
{"nRequestPositionUpdates", "(JLjava/lang/ref/WeakReference;)V",
(void*)android_view_RenderNode_requestPositionUpdates},
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index e7432ac..90c4440 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -136,24 +136,59 @@
free(valueBuffer);
return nullptr;
}
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
+namespace {
+// Helper for BlobCache::set to trace the result.
+void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (cache->set(key, keySize, value, valueSize)) {
+ case BlobCache::InsertResult::kInserted:
+ // This is what we expect/hope. It means the cache is large enough.
+ return;
+ case BlobCache::InsertResult::kDidClean: {
+ ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
+ valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kNotEnoughSpace: {
+ ATRACE_FORMAT("ShaderCache: could not fit {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kInvalidValueSize:
+ case BlobCache::InsertResult::kInvalidKeySize: {
+ ATRACE_FORMAT("ShaderCache: invalid size {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kKeyTooBig:
+ case BlobCache::InsertResult::kValueTooBig:
+ case BlobCache::InsertResult::kCombinedTooBig: {
+ ATRACE_FORMAT("ShaderCache: entry too big: {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ }
+}
+} // namespace
+
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache && mSavePending) {
if (mIDHash.size()) {
auto key = sIDKey;
- mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+ set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
mBlobCache->writeToFile();
}
mSavePending = false;
}
-void ShaderCache::store(const SkData& key, const SkData& data) {
+void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
ATRACE_NAME("ShaderCache::store");
std::lock_guard<std::mutex> lock(mMutex);
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
if (!mInitialized) {
return;
@@ -187,7 +222,7 @@
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- bc->set(key.data(), keySize, value, valueSize);
+ set(bc, key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelay > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 4dcc9fb..3e0fd51 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -73,7 +73,7 @@
* "store" attempts to insert a new key/value blob pair into the cache.
* This will be called by Skia after it compiled a new SKSL shader
*/
- void store(const SkData& key, const SkData& data) override;
+ void store(const SkData& key, const SkData& data, const SkString& description) override;
/**
* "onVkFrameFlushed" tries to store Vulkan pipeline cache state.
@@ -210,6 +210,13 @@
*/
static constexpr uint8_t sIDKey = 0;
+ /**
+ * Most of this class concerns persistent storage for shaders, but it's also
+ * interesting to keep track of how many shaders are stored in RAM. This
+ * class provides a convenient entry point for that.
+ */
+ int mNumShadersCachedInRam = 0;
+
friend class ShaderCacheTestUtils; // used for unit testing
};
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 87981f1..974d85a 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -140,9 +140,9 @@
// write to the in-memory cache without storing on disk and verify we read the same values
sk_sp<SkData> inVS;
setShader(inVS, "sassas");
- ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
setShader(inVS, "someVS");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
ASSERT_TRUE(checkShader(outVS, "sassas"));
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -166,7 +166,7 @@
// change data, store to disk, read back again and verify data has been changed
setShader(inVS, "ewData1");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -177,7 +177,7 @@
std::vector<uint8_t> dataBuffer(dataSize);
genRandomData(dataBuffer);
setShader(inVS, dataBuffer);
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -225,7 +225,7 @@
setShader(data, dataBuffer);
blob = std::make_pair(key, data);
- ShaderCache::get().store(*key.get(), *data.get());
+ ShaderCache::get().store(*key.get(), *data.get(), SkString());
}
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 1dc74e5..10ea651 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -106,6 +106,7 @@
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
}
std::mutex& PointerController::getLock() const {
@@ -255,6 +256,12 @@
getAdditionalMouseResources = true;
}
mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ float xPos, yPos;
+ mCursorController.getPosition(&xPos, &yPos);
+ mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
+ mLocked.pointerDisplayId = viewport.displayId;
+ }
}
void PointerController::updatePointerIcon(int32_t iconId) {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 2e6e851..eab030f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -104,6 +104,7 @@
struct Locked {
Presentation presentation;
+ int32_t pointerDisplayId = ADISPLAY_ID_NONE;
std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 26a65a4..c2bc1e0 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -79,6 +79,7 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
virtual int32_t getDefaultPointerIconId() = 0;
virtual int32_t getCustomPointerIconId() = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index dae1fcc..f9752ed 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -56,9 +56,11 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
virtual int32_t getDefaultPointerIconId() override;
virtual int32_t getCustomPointerIconId() override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
+ std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
private:
void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
@@ -66,6 +68,7 @@
bool pointerIconLoaded{false};
bool pointerResourcesLoaded{false};
bool additionalMouseResourcesLoaded{false};
+ std::optional<int32_t /*displayId*/> latestPointerDisplayId;
};
void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
@@ -126,12 +129,19 @@
icon->hotSpotX = hotSpot.first;
icon->hotSpotY = hotSpot.second;
}
+
+void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
+ float /*xPos*/,
+ float /*yPos*/) {
+ latestPointerDisplayId = displayId;
+}
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet();
+ void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -168,9 +178,9 @@
mThread.join();
}
-void PointerControllerTest::ensureDisplayViewportIsSet() {
+void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
DisplayViewport viewport;
- viewport.displayId = ADISPLAY_ID_DEFAULT;
+ viewport.displayId = displayId;
viewport.logicalRight = 1600;
viewport.logicalBottom = 1200;
viewport.physicalRight = 800;
@@ -255,6 +265,30 @@
ensureDisplayViewportIsSet();
}
+TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
+ EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
+ << "A pointer display change does not occur when PointerController is created.";
+
+ ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
+
+ const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
+ ASSERT_TRUE(lastReportedPointerDisplayId)
+ << "The policy is notified of a pointer display change when the viewport is first set.";
+ EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
+ << "Incorrect pointer display notified.";
+
+ ensureDisplayViewportIsSet(42);
+
+ EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
+ << "The policy is notified when the pointer display changes.";
+
+ // Release the PointerController.
+ mPointerController = nullptr;
+
+ EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
+ << "The pointer display changes to invalid when PointerController is destroyed.";
+}
+
class PointerControllerWindowInfoListenerTest : public Test {};
class TestPointerController : public PointerController {
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 11cacd0..44b4662 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -235,13 +235,7 @@
mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
}
} else {
- for (ListenerInfo info : mDeviceVolumeListeners) {
- if (info.mListener == vclistener) {
- throw new IllegalArgumentException(
- "attempt to call setDeviceAbsoluteMultiVolumeBehavior() "
- + "on a previously registered listener");
- }
- }
+ mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device));
}
mDeviceVolumeListeners.add(listenerInfo);
mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 55c558f..85cd342 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2910,6 +2910,10 @@
* For portability, an application should prime the data path to the maximum allowed
* by writing data until the write() method returns a short transfer count.
* This allows play() to start immediately, and reduces the chance of underrun.
+ *<p>
+ * As of {@link android.os.Build.VERSION_CODES#S} the minimum level to start playing
+ * can be obtained using {@link #getStartThresholdInFrames()} and set with
+ * {@link #setStartThresholdInFrames(int)}.
*
* @throws IllegalStateException if the track isn't properly initialized
*/
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 70d6810..472586b 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -643,6 +643,9 @@
/**
* <p>Return the frame to the ImageReader for reuse.</p>
+ *
+ * This method should only be called via {@link SurfaceImage#close} which ensures that image
+ * closing is atomic.
*/
private void releaseImage(Image i) {
if (! (i instanceof SurfaceImage) ) {
@@ -1125,6 +1128,8 @@
}
private class SurfaceImage extends android.media.Image {
+ private final Object mCloseLock = new Object();
+
public SurfaceImage(int format) {
mFormat = format;
mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
@@ -1139,7 +1144,9 @@
@Override
public void close() {
- ImageReader.this.releaseImage(this);
+ synchronized (this.mCloseLock) {
+ ImageReader.this.releaseImage(this);
+ }
}
public ImageReader getReader() {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index ef0270b..e5673a6 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1728,7 +1728,9 @@
}
int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber);
if (res == RESULT_SUCCESS) {
- // TODO: b/211778848 Update Tuner Resource Manager.
+ if (!mTunerResourceManager.setMaxNumberOfFrontends(frontendType, maxNumber)) {
+ res = RESULT_INVALID_ARGUMENT;
+ }
}
return res;
}
@@ -1749,7 +1751,13 @@
TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) {
return -1;
}
- return nativeGetMaxNumberOfFrontends(frontendType);
+ int maxNumFromHAL = nativeGetMaxNumberOfFrontends(frontendType);
+ int maxNumFromTRM = mTunerResourceManager.getMaxNumberOfFrontends(frontendType);
+ if (maxNumFromHAL != maxNumFromTRM) {
+ Log.w(TAG, "max num of usable frontend is out-of-sync b/w " + maxNumFromHAL
+ + " != " + maxNumFromTRM);
+ }
+ return maxNumFromHAL;
}
/** @hide */
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 5ada89e..15175a7 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -401,6 +401,43 @@
}
/**
+ * Sets the maximum usable frontends number of a given frontend type. It is used to enable or
+ * disable frontends when cable connection status is changed by user.
+ *
+ * @param frontendType the frontendType which the maximum usable number will be set for.
+ * @param maxNum the new maximum usable number.
+ *
+ * @return true if successful and false otherwise.
+ */
+ public boolean setMaxNumberOfFrontends(int frontendType, int maxNum) {
+ boolean result = false;
+ try {
+ result = mService.setMaxNumberOfFrontends(frontendType, maxNum);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Get the maximum usable frontends number of a given frontend type.
+ *
+ * @param frontendType the frontendType which the maximum usable number will be queried for.
+ *
+ * @return the maximum usable number of the queried frontend type. Returns -1 when the
+ * frontendType is invalid
+ */
+ public int getMaxNumberOfFrontends(int frontendType) {
+ int result = -1;
+ try {
+ result = mService.getMaxNumberOfFrontends(frontendType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
* Requests from the client to share frontend with an existing client.
*
* <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index d16fc6c..144b98c 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -166,6 +166,27 @@
boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle);
/*
+ * Sets the maximum usable frontends number of a given frontend type. It is used to enable or
+ * disable frontends when cable connection status is changed by user.
+ *
+ * @param frontendType the frontendType which the maximum usable number will be set for.
+ * @param maxNumber the new maximum usable number.
+ *
+ * @return true if successful and false otherwise.
+ */
+ boolean setMaxNumberOfFrontends(in int frontendType, in int maxNum);
+
+ /*
+ * Get the maximum usable frontends number of a given frontend type.
+ *
+ * @param frontendType the frontendType which the maximum usable number will be queried for.
+ *
+ * @return the maximum usable number of the queried frontend type. Returns -1 when the
+ * frontendType is invalid
+ */
+ int getMaxNumberOfFrontends(in int frontendType);
+
+ /*
* Requests to share frontend with an existing client.
*
* <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index f4a39b3..b7ad6dc 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -18,10 +18,10 @@
#define LOG_TAG "AndroidMediaUtils"
#include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h>
-#include <hardware/camera3.h>
#include <ui/GraphicBufferMapper.h>
#include <ui/GraphicTypes.h>
#include <utils/Log.h>
+
#include "android_media_Utils.h"
#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
@@ -122,8 +122,8 @@
}
// First check for BLOB transport header at the end of the buffer
- uint8_t* header = blobBuffer + (width - sizeof(struct camera3_jpeg_blob));
- struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header);
+ uint8_t* header = blobBuffer + (width - sizeof(struct camera3_jpeg_blob_v2));
+ struct camera3_jpeg_blob_v2 *blob = (struct camera3_jpeg_blob_v2*)(header);
if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID ||
blob->jpeg_blob_id == CAMERA3_HEIC_BLOB_ID) {
size = blob->jpeg_size;
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 4feb4f51..c12cec1 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -50,6 +50,20 @@
int getBufferHeight(BufferItem *buffer);
+// Must be in sync with AIDL CameraBlob : android.hardware.camera.device.CameraBlob
+// HALs must NOT copy this definition.
+// for details: http://b/229688810
+typedef struct camera3_jpeg_blob_v2 {
+ uint32_t jpeg_blob_id;
+ uint32_t jpeg_size;
+} camera3_jpeg_blobv2_t;
+
+// Must be in sync with AIDL CameraBlob : android.hardware.camera.device.CameraBlobId
+enum {
+ CAMERA3_JPEG_BLOB_ID = 0x00FF,
+ CAMERA3_JPEG_APP_SEGMENTS_BLOB_ID = 0x0100,
+};
+
}; // namespace android
#endif // _ANDROID_MEDIA_UTILS_H_
diff --git a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
index 5b52278..6f734a3 100644
--- a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
@@ -18,10 +18,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="backup_confirm_title" msgid="827563724209303345">"Babeskopia osoa"</string>
<string name="restore_confirm_title" msgid="5469365809567486602">"Leheneratze osoa"</string>
- <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu babeskopia egitea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena."</string>
+ <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nBabeskopia egitea zeuk eskatu ez baduzu, ez eman eragiketarekin jarraitzeko baimena."</string>
<string name="allow_backup_button_label" msgid="4217228747769644068">"Egin datuen babeskopia"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"Ez egin babeskopia"</string>
- <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu leheneratzea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string>
+ <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nLeheneratzea zeuk eskatu ez baduzu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string>
<string name="allow_restore_button_label" msgid="3081286752277127827">"Leheneratu datuak"</string>
<string name="deny_restore_button_label" msgid="1724367334453104378">"Ez leheneratu"</string>
<string name="current_password_text" msgid="8268189555578298067">"Idatzi babeskopien oraingo pasahitza behean:"</string>
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 8b5d214..16cd2e5 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -46,6 +46,7 @@
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
+ android:configChanges="orientation|screenSize"
android:theme="@style/ChooserActivity"/>
<service
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml
new file mode 100644
index 0000000..8d7fa26
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path
+ android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml
new file mode 100644
index 0000000..5689e34
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml
new file mode 100644
index 0000000..06bfad5
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22ZM8,17H16V10Q16,8.35 14.825,7.175Q13.65,6 12,6Q10.35,6 9.175,7.175Q8,8.35 8,10Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml
new file mode 100644
index 0000000..f8aef33
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M6,17H18L14.25,12L11.25,16L9,13ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM5,5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml b/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml
index 8e92051f..6ce1f12 100644
--- a/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml
+++ b/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml
@@ -18,6 +18,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/system_accent1_100"/>
- <corners android:topLeftRadius="16dp" android:topRightRadius="16dp"
- android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/>
+ <corners android:topLeftRadius="20dp" android:topRightRadius="20dp"
+ android:bottomLeftRadius="20dp" android:bottomRightRadius="20dp"/>
</shape>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml
index d1ec863..7295e78 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml
@@ -19,7 +19,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_600">
<path
android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z"
android:fillColor="#3C4043"
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml
index e5825bc..7b1ef85 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml
@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_600">
<path android:fillColor="@android:color/white"
android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22ZM8,17H16V10Q16,8.35 14.825,7.175Q13.65,6 12,6Q10.35,6 9.175,7.175Q8,8.35 8,10Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml
index 406a3b5..3e033d3 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml
@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_600">
<path android:fillColor="@android:color/white"
android:pathData="M6,17H18L14.25,12L11.25,16L9,13ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM5,5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index c0e6c09..520ade8 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -12,135 +12,142 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/activity_confirmation"
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:minWidth="340dp">
+ android:layout_height="match_parent"
+ style="@style/ScrollViewStyle">
- <LinearLayout android:id="@+id/association_confirmation"
- style="@style/ContainerLayout">
-
- <!-- A header for selfManaged devices only. -->
- <include layout="@layout/vendor_header" />
-
- <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
- <ImageView
- android:id="@+id/profile_icon"
+ <LinearLayout
+ android:id="@+id/activity_confirmation"
android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="18dp"
- android:tint="@android:color/system_accent1_600"/>
+ android:layout_height="wrap_content"
+ android:baselineAligned="false">
- <LinearLayout style="@style/Description">
- <TextView
- android:id="@+id/title"
- style="@style/DescriptionTitle" />
+ <LinearLayout android:id="@+id/association_confirmation"
+ style="@style/ContainerLayout">
- <TextView
- android:id="@+id/summary"
- style="@style/DescriptionSummary" />
+ <!-- A header for selfManaged devices only. -->
+ <include layout="@layout/vendor_header" />
+
+ <!-- Do NOT change the ID of the root LinearLayout above:
+ it's referenced in CTS tests. -->
+
+ <ImageView
+ android:id="@+id/profile_icon"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center"
+ android:layout_marginTop="18dp"
+ android:tint="@android:color/system_accent1_600"/>
+
+ <LinearLayout style="@style/Description">
+ <TextView
+ android:id="@+id/title"
+ style="@style/DescriptionTitle" />
+
+ <TextView
+ android:id="@+id/summary"
+ style="@style/DescriptionSummary" />
+
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:id="@+id/multiple_device_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <View
+ android:id="@+id/border_top"
+ style="@style/DeviceListBorder" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/device_list"
+ android:layout_width="match_parent"
+ android:scrollbars="vertical"
+ android:layout_marginBottom="12dp"
+ android:layout_height="200dp" />
+
+ <View
+ android:id="@+id/border_bottom"
+ style="@style/DeviceListBorder" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/permission_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <ProgressBar
+ android:id="@+id/spinner_multiple_device"
+ android:visibility="gone"
+ style="@style/Spinner" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="16dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_positive"
+ style="@style/PositiveButton"
+ android:text="@string/consent_yes" />
+
+ <Button
+ android:id="@+id/btn_negative"
+ android:layout_marginBottom="12dp"
+ style="@style/NegativeButton"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom|end"
+ android:orientation="vertical"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="16dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_negative_multiple_devices"
+ style="@style/NegativeButtonMultipleDevices"
+ android:textColor="?android:textColorPrimary"
+ android:visibility="gone"
+ android:text="@string/consent_no" />
+ </LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
- android:layout_height="0dp"
+ android:layout_height="match_parent"
android:layout_weight="1">
- <LinearLayout
- android:id="@+id/multiple_device_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
- android:orientation="vertical"
- android:visibility="gone">
-
- <View
- android:id="@+id/border_top"
- style="@style/DeviceListBorder" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/device_list"
- android:layout_width="match_parent"
- android:scrollbars="vertical"
- android:layout_marginBottom="12dp"
- android:layout_height="200dp" />
-
- <View
- android:id="@+id/border_bottom"
- style="@style/DeviceListBorder" />
-
- </LinearLayout>
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/permission_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
<ProgressBar
- android:id="@+id/spinner_multiple_device"
+ android:id="@+id/spinner_single_device"
android:visibility="gone"
- style="@style/Spinner" />
-
- </RelativeLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_marginTop="16dp">
-
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
-
- <Button
- android:id="@+id/btn_positive"
- style="@style/PositiveButton"
- android:text="@string/consent_yes" />
-
- <Button
- android:id="@+id/btn_negative"
- android:layout_marginBottom="12dp"
- style="@style/NegativeButton"
- android:text="@string/consent_no" />
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="bottom|right"
- android:orientation="vertical"
- android:layout_marginRight="16dp"
- android:layout_marginBottom="16dp">
-
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
-
- <Button
- android:id="@+id/btn_negative_multiple_devices"
- style="@style/NegativeButtonMultipleDevices"
- android:textColor="?android:textColorPrimary"
- android:visibility="gone"
- android:text="@string/consent_no" />
- </LinearLayout>
+ style="@style/Spinner" />
+ </RelativeLayout>>
</LinearLayout>
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1">
-
- <ProgressBar
- android:id="@+id/spinner_single_device"
- android:visibility="gone"
- style="@style/Spinner" />
- </RelativeLayout>>
-
-</LinearLayout>
\ No newline at end of file
+</ScrollView>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
index a9ace44..d0d46f7 100644
--- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
@@ -15,54 +15,67 @@
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/helper_confirmation"
- android:theme="@style/ChooserActivity"
- android:padding="12dp"
- style="@style/ContainerLayout">
-
- <ImageView
- android:id="@+id/app_icon"
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"/>
-
- <TextView
- android:id="@+id/helper_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingHorizontal="12dp"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="22sp" />
-
- <TextView
- android:id="@+id/helper_summary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="24dp"
- android:layout_marginRight="24dp"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="24dp"
- android:gravity="center"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp" />
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/ScrollViewStyle">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginRight="12dp"
- android:layout_marginBottom="12dp"
- android:gravity="end">
+ android:layout_height="wrap_content">
- <Button
- android:id="@+id/btn_back"
- style="@style/VendorHelperBackButton"
- android:text="@string/consent_back" />
+ <LinearLayout
+ android:id="@+id/helper_confirmation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/ChooserActivity"
+ android:padding="12dp"
+ style="@style/ContainerLayout">
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"/>
+
+ <TextView
+ android:id="@+id/helper_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="22sp" />
+
+ <TextView
+ android:id="@+id/helper_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="32dp"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="14sp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginEnd="12dp"
+ android:gravity="end">
+
+ <Button
+ android:id="@+id/btn_back"
+ style="@style/VendorHelperBackButton"
+ android:text="@string/consent_back" />
+
+ </LinearLayout>
+
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</ScrollView>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index eeb988f..0a5afe4 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -28,14 +28,15 @@
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
- android:layout_marginLeft="24dp"
- android:layout_marginRight="12dp"
+ android:layout_marginStart="24dp"
android:tint="@android:color/system_accent1_600"/>
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 3dce38d..54916a2 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -20,8 +20,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingLeft="32dp"
- android:paddingRight="32dp"
+ android:paddingStart="32dp"
+ android:paddingEnd="32dp"
android:paddingBottom="14dp">
<ImageView
@@ -30,7 +30,6 @@
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
- android:tint="@android:color/system_accent1_600"
android:contentDescription="Permission Icon"/>
<LinearLayout
@@ -51,7 +50,6 @@
android:id="@+id/permission_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingRight="24dp"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"/>
diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
index c35f59e..14e7431 100644
--- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml
+++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
@@ -24,28 +24,32 @@
android:layout_gravity="center"
android:paddingTop="24dp"
android:paddingBottom="4dp"
- android:paddingLeft="24dp"
- android:paddingRight="24dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
android:visibility="gone" >
<ImageView
android:id="@+id/vendor_header_image"
- android:layout_width="31dp"
- android:layout_height="32dp" />
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@string/vendor_icon_description" />
<TextView
android:id="@+id/vendor_header_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="20dp"
- android:layout_marginTop="5dp"
- android:layout_toRightOf="@+id/header_image" />
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="12dp"
+ android:textSize="16sp"
+ android:layout_toEndOf="@+id/vendor_header_image"
+ android:textColor="?android:attr/textColorSecondary"/>
<ImageButton
android:id="@+id/vendor_header_button"
android:background="@drawable/ic_info"
- android:layout_width="31dp"
- android:layout_height="32dp"
- android:layout_alignParentRight="true" />
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@string/vendor_header_button_description"
+ android:layout_alignParentEnd="true" />
</RelativeLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 586a022..3d6bf15 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -116,4 +116,10 @@
apps installed on your watch during setup will use the same permissions as your phone.\n\n
These permissions may include access to your watch\u2019s microphone and location.</string>
+ <!--Description for vendor icon [CHAR LIMIT=30]-->
+ <string name="vendor_icon_description">App Icon</string>
+
+ <!--Description for information icon [CHAR LIMIT=30]-->
+ <string name="vendor_header_button_description">More Information Button</string>
+
</resources>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index c38323f..428f2dc 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -59,9 +59,8 @@
<style name="VendorHelperBackButton"
parent="@android:style/Widget.Material.Button.Borderless.Colored">
- <item name="android:layout_width">60dp</item>
- <item name="android:layout_height">36dp</item>
- <item name="android:layout_marginTop">20dp</item>
+ <item name="android:layout_width">70dp</item>
+ <item name="android:layout_height">48dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textColor">@android:color/system_neutral1_900</item>
<item name="android:background">@drawable/helper_back_button</item>
@@ -71,8 +70,6 @@
parent="@android:style/Widget.Material.Button.Borderless.Colored">
<item name="android:layout_width">300dp</item>
<item name="android:layout_height">56dp</item>
- <item name="android:layout_marginLeft">24dp</item>
- <item name="android:layout_marginRight">24dp</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
@@ -84,8 +81,6 @@
parent="@android:style/Widget.Material.Button.Borderless.Colored">
<item name="android:layout_width">300dp</item>
<item name="android:layout_height">56dp</item>
- <item name="android:layout_marginLeft">24dp</item>
- <item name="android:layout_marginRight">24dp</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
@@ -115,4 +110,10 @@
<item name="android:indeterminate">true</item>
<item name="android:layout_centerInParent">true</item>
</style>
+
+ <style name="ScrollViewStyle">
+ <item name="android:scrollbars">none</item>
+ <item name="android:fillViewport">true</item>
+ <item name="android:clipChildren">false</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/themes.xml b/packages/CompanionDeviceManager/res/values/themes.xml
index e3fc67c..1ea3968 100644
--- a/packages/CompanionDeviceManager/res/values/themes.xml
+++ b/packages/CompanionDeviceManager/res/values/themes.xml
@@ -18,8 +18,8 @@
<style name="ChooserActivity"
parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
- <item name="*android:windowFixedHeightMajor">100%</item>
- <item name="*android:windowFixedHeightMinor">100%</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 37cbf30..9e9ec04 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -51,6 +51,7 @@
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.MacAddress;
import android.os.Bundle;
@@ -411,17 +412,19 @@
final Drawable vendorIcon;
final CharSequence vendorName;
final Spanned title;
+ int nightModeFlags = getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
mPermissionTypes = new ArrayList<>();
try {
vendorIcon = getVendorHeaderIcon(this, packageName, userId);
vendorName = getVendorHeaderName(this, packageName, userId);
-
mVendorHeaderImage.setImageDrawable(vendorIcon);
if (hasVendorIcon(this, packageName, userId)) {
- mVendorHeaderImage.setColorFilter(getResources().getColor(
- android.R.color.system_accent1_600, /* Theme= */null));
+ int color = nightModeFlags == Configuration.UI_MODE_NIGHT_YES
+ ? android.R.color.system_accent1_200 : android.R.color.system_accent1_600;
+ mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
@@ -484,6 +487,14 @@
if (deviceFilterPairs.isEmpty()) return;
mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+ // No need to show user consent dialog if it is a singleDevice
+ // and isSkipPrompt(true) AssociationRequest.
+ // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ if (mRequest.isSkipPrompt()) {
+ mSingleDeviceSpinner.setVisibility(View.GONE);
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
final String deviceName = mSelectedDevice.getDisplayName();
final Spanned title = getHtmlFromResources(
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index f333b86..fc5ff08 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -123,6 +123,7 @@
intent.setAction(ACTION_START_DISCOVERY);
intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
sStateLiveData.setValue(DiscoveryState.STARTING);
+ sScanResultsLiveData.setValue(Collections.emptyList());
context.startService(intent);
}
@@ -173,7 +174,6 @@
@Override
public void onDestroy() {
- sScanResultsLiveData.setValue(Collections.emptyList());
super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy()");
}
@@ -187,7 +187,6 @@
mStopAfterFirstMatch = request.isSingleDevice();
mDiscoveryStarted = true;
sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS);
- sScanResultsLiveData.setValue(Collections.emptyList());
final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
final List<BluetoothDeviceFilter> btFilters =
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 7cf5385..af3e210 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -47,6 +47,7 @@
uses_libs: ["android.test.runner"],
apex_available: [
+ "//apex_available:platform",
"com.android.apex.cts.shim.v2_apk_in_apex_upgrades",
],
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
index 25f0771..72b569f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
@@ -25,7 +25,7 @@
android:fitsSystemWindows="true"
android:outlineAmbientShadowColor="@android:color/transparent"
android:outlineSpotShadowColor="@android:color/transparent"
- android:background="?android:attr/colorPrimary"
+ android:background="@android:color/transparent"
android:theme="@style/Theme.CollapsingToolbar.Settings">
<com.google.android.material.appbar.CollapsingToolbarLayout
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 72383fe..dbb4b50 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -29,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
@@ -41,6 +43,7 @@
* This widget is wrapping the collapsing toolbar and can be directly used by the
* {@link AppCompatActivity}.
*/
+@RequiresApi(Build.VERSION_CODES.S)
public class CollapsingCoordinatorLayout extends CoordinatorLayout {
private static final String TAG = "CollapsingCoordinatorLayout";
private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index ac30636..6766cdd 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -40,6 +40,8 @@
static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
@VisibleForTesting
View.OnClickListener mLearnMoreListener;
+ @VisibleForTesting
+ int mIconVisibility = View.VISIBLE;
private CharSequence mContentDescription;
private CharSequence mLearnMoreText;
private CharSequence mLearnMoreContentDescription;
@@ -84,6 +86,9 @@
} else {
learnMore.setVisibility(View.GONE);
}
+
+ View icon = holder.itemView.findViewById(R.id.icon_frame);
+ icon.setVisibility(mIconVisibility);
}
@Override
@@ -165,6 +170,17 @@
}
}
+ /**
+ * Set visibility of footer icon.
+ */
+ public void setIconVisibility(int iconVisibility) {
+ if (mIconVisibility == iconVisibility) {
+ return;
+ }
+ mIconVisibility = iconVisibility;
+ notifyChanged();
+ }
+
private void init() {
setLayoutResource(R.layout.preference_footer);
if (getIcon() == null) {
diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java
index 5693c2f..281f7ba6 100644
--- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java
+++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java
@@ -27,17 +27,20 @@
import android.os.UserManager;
import android.provider.Settings;
+import androidx.annotation.Nullable;
+
import java.util.List;
+
/**
* Utility class for find out when to show WorkPolicyInfo
*/
public class WorkPolicyUtils {
- Context mContext;
- PackageManager mPackageManager;
- UserManager mUserManager;
- DevicePolicyManager mDevicePolicyManager;
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
+ private final DevicePolicyManager mDevicePolicyManager;
private static final int USER_NULL = -10000;
@@ -81,7 +84,12 @@
return false;
}
- private Intent getWorkPolicyInfoIntentDO() {
+ /**
+ * Returns the work policy info intent if the device owner component exists,
+ * and returns {@code null} otherwise
+ */
+ @Nullable
+ public Intent getWorkPolicyInfoIntentDO() {
final ComponentName ownerComponent = getDeviceOwnerComponent();
if (ownerComponent == null) {
return null;
@@ -99,43 +107,55 @@
return null;
}
- private Intent getWorkPolicyInfoIntentPO() {
+ @Nullable
+ private ComponentName getManagedProfileOwnerComponent(int managedUserId) {
+ if (managedUserId == USER_NULL) {
+ return null;
+ }
+ Context managedProfileContext;
try {
- final int managedUserId = getManagedProfileUserId();
- if (managedUserId == USER_NULL) {
- return null;
- }
-
- Context managedProfileContext =
+ managedProfileContext =
mContext.createPackageContextAsUser(
mContext.getPackageName(), 0, UserHandle.of(managedUserId)
);
-
- DevicePolicyManager managedProfileDevicePolicyManager =
- (DevicePolicyManager)
- managedProfileContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- ComponentName ownerComponent = managedProfileDevicePolicyManager.getProfileOwner();
- if (ownerComponent == null) {
- return null;
- }
-
- // Only search for the required action in the Profile Owner's package
- final Intent intent =
- new Intent(Settings.ACTION_SHOW_WORK_POLICY_INFO)
- .setPackage(ownerComponent.getPackageName());
- final List<ResolveInfo> activities =
- mPackageManager.queryIntentActivitiesAsUser(
- intent, 0, UserHandle.of(managedUserId));
- if (activities.size() != 0) {
- return intent;
- }
-
- return null;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
+
+ DevicePolicyManager managedProfileDevicePolicyManager =
+ (DevicePolicyManager)
+ managedProfileContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ ComponentName ownerComponent = managedProfileDevicePolicyManager.getProfileOwner();
+ return ownerComponent;
}
+ /**
+ * Returns the work policy info intent if the profile owner component exists,
+ * and returns {@code null} otherwise
+ */
+ @Nullable
+ public Intent getWorkPolicyInfoIntentPO() {
+ final int managedUserId = getManagedProfileUserId();
+ ComponentName ownerComponent = getManagedProfileOwnerComponent(managedUserId);
+ if (ownerComponent == null) {
+ return null;
+ }
+
+ // Only search for the required action in the Profile Owner's package
+ final Intent intent =
+ new Intent(Settings.ACTION_SHOW_WORK_POLICY_INFO)
+ .setPackage(ownerComponent.getPackageName());
+ final List<ResolveInfo> activities =
+ mPackageManager.queryIntentActivitiesAsUser(
+ intent, 0, UserHandle.of(managedUserId));
+ if (activities.size() != 0) {
+ return intent;
+ }
+
+ return null;
+ }
+
+ @Nullable
private ComponentName getDeviceOwnerComponent() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
return null;
@@ -143,7 +163,10 @@
return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();
}
- private int getManagedProfileUserId() {
+ /**
+ * Returns the user id of the managed profile, and returns {@code USER_NULL} otherwise
+ */
+ public int getManagedProfileUserId() {
List<UserHandle> allProfiles = mUserManager.getAllProfiles();
for (UserHandle uh : allProfiles) {
int id = uh.getIdentifier();
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 378e1bb..b5d53c5 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Saai <xliff:g id="SWITCHAPP">%1$s</xliff:g> uit"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Verander uitvoer"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Voorspellingteruggebaaranimasies"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Aktiveer stelselanimasies vir voorspellingteruggebaar."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Hierdie instelling aktiveer stelselanimasies vir voorspellinggebaaranimasie. Dit vereis dat enableOnBackInvokedCallback per program op waar gestel word in die manifeslêer."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 6d833f9..6258a47 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>ን ካሰራጩ ወይም ውፅዓትን ከቀየሩ የአሁኑ ስርጭትዎ ይቆማል"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ያሰራጩ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ውፅዓትን ይቀይሩ"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"የግምት ጀርባ እነማዎች"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"ለግምት ጀርባ የስርዓት እንማዎችን ያንቁ።"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ይህ ቅንብር የስርዓት እነማዎችን ለመገመት የምልክት እነማን ያነቃል። በዝርዝር ሰነድ ፋይሉ ውስጥ በእያንዳንዱ መተግበሪያ enableOnBackInvokedCallbackን ወደ እውነት ማቀናበር ያስፈልገዋል።"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index a3c4ac5..e042ba4 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"تغيير جهاز الإخراج"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index e4a6118..3b6cd31 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্ৰচাৰ কৰক"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"আউটপুট সলনি কৰক"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"প্ৰেডিক্টিভ বেক এনিমেশ্বন"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"প্ৰেডিক্টিভ বেকৰ বাবে ছিষ্টেম এনিমেশ্বন সক্ষম কৰক।"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"এই ছেটিংটোৱে প্ৰেডিক্টিভ বেক এনিমেশ্বনৰ বাবে ছিষ্টেম এনিমেশ্বন সক্ষম কৰে। ইয়াৰ বাবে মেনিফেষ্ট ফাইলত প্ৰতি এপত enableOnBackInvokedCallback সত্য বুলি ছেট কৰাৰ প্ৰয়োজন।"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index ca78c68..90bdd24 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlayın"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Nəticəni dəyişdirin"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Proqnozlaşdırılan geri animasiyalar"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Proqnozlaşdırıcı geri jest üçün sistem animasiyalarını aktiv edin."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Bu ayar proqnozlaşdırıcı jest animasiyası üçün sistem animasiyalarını aktiv edir. Bu, manifest faylında hər bir tətbiq üçün enableOnBackInvokedCallback-in doğru kimi ayarlanmasını tələb edir."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 0da57e4..940f78d 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -594,7 +594,7 @@
<string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Resetuj sesiju gosta"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Želite li da resetujete sesiju gosta?"</string>
- <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite li da uklonite gosta?"</string>
+ <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite da uklonite gosta?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetuj"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ukloni"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Sesija gosta se resetuje…"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitujete aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promenite izlaz, aktuelno emitovanje će se zaustaviti"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitujte aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promenite izlaz"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka sa predviđanjem"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogućite animacije sistema za pokret povratka sa predviđanjem."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ovo podešavanje omogućava animacije sistema za pokret povratka sa predviđanjem. Zahteva podešavanje dozvole enableOnBackInvokedCallback po aplikaciji na true u fajlu manifesta."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 2c681da..3a9db9e 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Трансляцыя праграмы \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Змяненне вываду"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 5b6d982..5585598 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Предаване на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Промяна на изхода"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index bd81285..424b421 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"আপনি <xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করলে বা আউটপুট পরিবর্তন করলে, আপনার বর্তমান সম্প্রচার বন্ধ হয়ে যাবে"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করুন"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"আউটপুট পরিবর্তন করুন"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"ফিরে যাওয়ার পূর্বাভাস সংক্রান্ত অ্যানিমেশন"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"ফিরে যাওয়া সংক্রান্ত পূর্বাভাসের জন্য সিস্টেম অ্যানিমেশন চালু করুন।"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"জেসচারের পূর্বাভাস সংক্রান্ত অ্যানিমেশন দেখাতে এই সেটিং সিস্টেম অ্যানিমেশন চালু করে। এই সেটিংয়ে \'ম্যানিফেস্ট\' ফাইলে প্রত্যেক অ্যাপে enableOnBackInvokedCallback অ্যাট্রিবিউটকে \'ট্রু\' (true) হিসেবে সেট করতে হয়।"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 258120f..1ea91df 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitiraj aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promijeni izlaz"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka s predviđanjem"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogući animacije sustava za pokret povratka s predviđanjem."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ova postavka omogućuje animacije sustava za animaciju pokreta s predviđanjem. Zahtijeva postavljanje dopuštenja enableOnBackInvokedCallback po aplikaciji na True u datoteci manifesta."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 91dcbc9..cfd91ec 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emet <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Canvia la sortida"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 121402c..7cb54d4 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Změna výstupu"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 6f4846e..52adc58 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Hvis du udsender <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller skifter output, stopper din aktuelle udsendelse"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Udsend <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Skift output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Foreslåede animationer for Tilbage"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Aktivér systemanimationer for foreslåede animationer for Tilbage."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Denne indstilling aktiverer systemanimationer for de foreslåede animationer for bevægelsen Tilbage. Dette forudsætter konfiguration af enableOnBackInvokedCallback som sand for hver app i manifestfilen."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 7d66c83..bf9ce29 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> streamen"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ausgabe ändern"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index ef0f800..78ba6fa 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Εάν κάνετε μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g> ή αλλάξετε την έξοδο, η τρέχουσα μετάδοση θα σταματήσει"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Αλλαγή εξόδου"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 1dc7b42..5f345a0 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 20e9308..881917b 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 1dc7b42..5f345a0 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 1dc7b42..5f345a0 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 70227dc..c0b8f6b 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index d96e7f0..d2ac04d 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambia la salida"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index c9c1eb1..4abbbc1 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Perfil restringido"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"¿Añadir nuevo usuario?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Puedes compartir este dispositivo si creas más usuarios. Cada uno tendrá su propio espacio y podrá personalizarlo con aplicaciones, un fondo de pantalla y mucho más. Los usuarios también pueden ajustar opciones del dispositivo, como la conexión Wi‑Fi, que afectan a todos los usuarios.\n\nCuando añadas un usuario, tendrá que configurar su espacio.\n\nCualquier usuario puede actualizar aplicaciones de todos los usuarios. Es posible que no se transfieran los servicios y opciones de accesibilidad al nuevo usuario."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"Al añadir un usuario nuevo, debe configurar su espacio.\n\nCualquier usuario puede actualizar las aplicaciones del resto de usuarios."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"Al añadir un nuevo usuario, dicha persona debe configurar su espacio.\n\nCualquier usuario puede actualizar las aplicaciones del resto de usuarios."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"¿Configurar usuario ahora?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Asegúrate de que la persona está disponible en este momento para usar el dispositivo y configurar su espacio."</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"¿Quieres configurar un perfil ahora?"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambiar salida"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 37a6db2..c7ba159 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Rakenduse <xliff:g id="SWITCHAPP">%1$s</xliff:g> ülekandmine"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Väljundi muutmine"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 689cca1..1a20053 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -446,7 +446,7 @@
<string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Ikusmen-monokromia"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Daltonismoa (gorri-berdeak)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
- <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string>
+ <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (urdin-horia)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Baliteke koloreen zuzenketa lagungarria izatea hauek egin nahi dituzunean:<br/> <ol> <li>&nbsp;Koloreak zehaztasun handiagoz ikusi.</li> <li>&nbsp;Koloreak kendu, arreta gal ez dezazun.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Erabiltzailea"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Profil murriztua"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Beste erabiltzaile bat gehitu nahi duzu?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Gailu hau beste pertsona batzuekin partekatzeko, sortu erabiltzaile gehiago. Erabiltzaile bakoitzak bere eremua izango du eta, bertan, aplikazioak, horma-papera eta antzekoak pertsonalizatu ahal izango ditu. Horrez gain, erabiltzaile guztiei eragin diezaieketen ezarpen batzuk ere doi daitezke; adibidez, wifi-konexioarena.\n\nErabiltzaile bat gehitzen duzunean, pertsona horrek berak konfiguratu beharko du bere eremua.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak. Baliteke erabilerraztasun-ezarpenak eta -zerbitzuak ez transferitzea erabiltzaile berriei."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Gailu hau beste pertsona batzuekin partekatzeko, sortu erabiltzaile gehiago. Erabiltzaile bakoitzak bere eremua izango du eta, bertan, aplikazioak, horma-papera eta antzekoak pertsonalizatu ahal izango ditu. Horrez gain, agian erabiltzaile guztiei eragingo dieten ezarpen batzuk ere doi daitezke; adibidez, wifi-konexioarena.\n\nErabiltzaile bat gehitzen duzunean, pertsona horrek berak konfiguratu beharko du bere eremua.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak. Baliteke erabilerraztasun-ezarpenak eta -zerbitzuak ez transferitzea erabiltzaile berriei."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Erabiltzaile bat gehitzen duzunean, erabiltzaile horrek bere eremua konfiguratu beharko du.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Erabiltzailea konfiguratu?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Ziurtatu pertsona horrek gailua hartu eta bere eremua konfigura dezakeela"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Igorri <xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Aldatu audio-irteera"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index f141910..030d34c 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همهفرستی کنید یا خروجی را تغییر دهید، همهفرستی کنونی متوقف خواهد شد"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"همهفرستی <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"تغییر خروجی"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 32c5241..a90cb71 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Lähetä <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Muuta ulostuloa"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 51ddb5a..cb32e0f 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Changer la sortie"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 9cdcc1c..4367fe3 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Modifier le résultat"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f5ea9d4..ac8c6f3 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -604,7 +604,7 @@
<string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Realizaches demasiados intentos incorrectos. Eliminaranse os datos deste dispositivo."</string>
<string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Realizaches demasiados intentos incorrectos. Eliminarase este usuario."</string>
<string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Realizaches demasiados intentos incorrectos. Eliminaranse este perfil de traballo e os datos asociados."</string>
- <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Ignorar"</string>
+ <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Pechar"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Funcionamento predeterminado"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitir contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambiar de saída"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 393d6ff..ecef2eb 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"જો તમે <xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો અથવા આઉટપુટ બદલો, તો તમારું હાલનું બ્રોડકાસ્ટ બંધ થઈ જશે"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"આઉટપુટ બદલો"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"પાછળના પૂર્વાનુમાનિત ઍનિમેશન્સ"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"પાછળના પૂર્વાનુમાનિત સંકેત માટે સિસ્ટમ ઍનિમેશન ચાલુ કરો."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"આ સેટિંગ પૂર્વાનુમાનિત સંકેત ઍનિમેશન માટે સિસ્ટમ ઍનિમેશન ચાલુ કરે છે. તેના માટે દરેક ઍપ માટે મેનિફેસ્ટ ફાઇલમાં enableOnBackInvokedCallbackને true પર સેટ કરવાની જરૂર પડે છે."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3ab01369..2177b66 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट करें"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपुट बदलें"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"प्रिडिक्टिव बैक ऐनिमेशन"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"प्रिडिक्टिव बैक के लिए सिस्टम ऐनिमेशन चालू करें."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"यह सेटिंग, सिस्टम के ऐनिमेशन को प्रिडिक्टिव जेस्चर ऐनिमेशन के लिए चालू कर देती है. मेनिफ़ेस्ट फ़ाइल में enableOnBackInvokedCallback की सेटिंग को हर ऐप्लिकेशन के हिसाब से \'सही\' पर सेट होना चाहिए."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 82a7443..fb72a91b 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -583,7 +583,7 @@
<string name="profile_info_settings_title" msgid="105699672534365099">"Profilni podaci"</string>
<string name="user_need_lock_message" msgid="4311424336209509301">"Prije izrade ograničenog profila trebate postaviti zaključavanje zaslona radi zaštite svojih aplikacija i osobnih podataka."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Postavi zaključavanje"</string>
- <string name="user_switch_to_user" msgid="6975428297154968543">"Prelazak na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <string name="user_switch_to_user" msgid="6975428297154968543">"Prijeđi na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
<string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Izrada novog gosta…"</string>
<string name="add_user_failed" msgid="4809887794313944872">"Izrada novog korisnika nije uspjela"</string>
@@ -591,7 +591,7 @@
<string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
<string name="user_add_user" msgid="7876449291500212468">"Dodavanje korisnika"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Poništi gostujuću sesiju"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Poništiti gostujuću sesiju?"</string>
<string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite li ukloniti gosta?"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, vaše će se trenutačno emitiranje zaustaviti"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitiranje aplikacije <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promjena izlaza"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka s predviđanjem"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogući animacije sustava za pokret povratka s predviđanjem."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ova postavka omogućuje animacije sustava za animaciju pokreta s predviđanjem. Zahtijeva postavljanje dopuštenja enableOnBackInvokedCallback po aplikaciji na True u datoteci manifesta."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 8620780..c74a5c3 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Kimenet módosítása"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index e7c0a3b..9cf22e9 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -593,7 +593,7 @@
<string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Վերակայել հյուրի աշխատաշրջանը"</string>
- <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Վերակայե՞լ հյուրի աշխատաշրջանը"</string>
+ <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Զրոյացնե՞լ հյուրի աշխատաշրջանը"</string>
<string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Հեռացնե՞լ հյուրին"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Զրոյացնել"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Հեռացնել"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Հեռարձակել <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Փոխել աուդիո ելքը"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 1a1268c..4ba8287 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ubah output"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e0ca6e3..0733dc4 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Senda út <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Skipta um úttak"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index f04a394..25908b7 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambia uscita"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index c50b180..58ea427 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"שידור תוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"שינוי הפלט"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 4464c87..a17b4a7 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -569,8 +569,8 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"ユーザー"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"制限付きプロファイル"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"新しいユーザーを追加しますか?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"追加ユーザーを作成して、このデバイスを他のユーザーと共有できます。各ユーザーは各自のスペースを所有して、アプリや壁紙などのカスタマイズを行うことができます。Wi-Fi など、すべてのユーザーに影響するデバイス設定を変更することもできます。\n\n新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーは他のユーザーに代わってアプリを更新できます。ユーザー補助機能の設定とサービスは新しいユーザーに適用されないことがあります。"</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーは他のユーザーに代わってアプリを更新できます。"</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"追加ユーザーを作成して、このデバイスを他のユーザーと共有できます。各ユーザーは各自のスペースを所有して、アプリや壁紙などのカスタマイズを行うことができます。Wi-Fi など、すべてのユーザーに影響するデバイス設定を変更することもできます。\n\n新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーがアプリを更新でき、その影響は他のユーザーにも及びます。ユーザー補助機能の設定とサービスは新しいユーザーに適用されないことがあります。"</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーがアプリを更新でき、その影響は他のユーザーにも及びます。"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ユーザーを今すぐセットアップ"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ユーザーがデバイスを使って各自のスペースをセットアップできるようにします"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"プロファイルを今すぐセットアップしますか?"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャストしたり、出力を変更したりすると、現在のブロードキャストが停止します。"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャスト"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"出力を変更"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index c6a50ac..1b82259 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაციის შემთხვევაში ან აუდიოს გამოსასვლელის შეცვლისას, მიმდინარე ტრანსლაცია შეჩერდება"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაცია"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"აუდიოს გამოსასვლელის შეცვლა"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"უკან დაბრუნების ანიმაციის პროგნოზირება"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"უკან დაბრუნების პროგნოზირებადი ანიმაციისთვის სისტემის ანიმაციების ჩართვა."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ეს პარამეტრი ჩართავს სისტემის ანიმაციებს ჟესტების პროგნოზირებადი ანიმაციებისთვის. საჭიროა, რომ აღწერის ფაილში აპის enableOnBackInvokedCallback პარამეტრი იყოს ჭეშმარიტი."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index a831df1..33fd25b 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -54,9 +54,9 @@
<item msgid="9048424957228926377">"Әрқашан тексеру"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
- <item msgid="4045840870658484038">"Ешқашан HDCP (жоғары кең жолақты сандық мазмұн қорғаушы) тексерулерін қолданбаңыз"</item>
+ <item msgid="4045840870658484038">"Ешқашан HDCP (жоғары кең жолақты цифрлық мазмұн қорғаушы) тексерулерін қолданбаңыз"</item>
<item msgid="8254225038262324761">"HDCP тексерісін DRM мазмұны үшін ғана қолдану"</item>
- <item msgid="6421717003037072581">"Әрқашан HDCP (жоғары кең жолақты сандық мазмұн қорғаушы) тексерулерін қолданыңыз"</item>
+ <item msgid="6421717003037072581">"Әрқашан HDCP (жоғары кең жолақты цифрлық мазмұн қорғаушы) тексерулерін қолданыңыз"</item>
</string-array>
<string-array name="bt_hci_snoop_log_entries">
<item msgid="695678520785580527">"Өшірулі"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index e42dcbd..7af4bc3 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -344,7 +344,7 @@
<string name="enable_terminal_title" msgid="3834790541986303654">"Жергілікті терминал"</string>
<string name="enable_terminal_summary" msgid="2481074834856064500">"Жергілікті шелл-код қол жетімділігін ұсынатын терминалды қолданбаны қосу"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP тексерісі"</string>
- <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP (кең жолақты сандық мазмұн қорғау) тексеру мүмкіндігін орнату"</string>
+ <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP (кең жолақты цифрлық мазмұн қорғау) тексеру мүмкіндігін орнату"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"Түзету"</string>
<string name="debug_app" msgid="8903350241392391766">"Түзету қолданбасын таңдау"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"Түзету қолданбалары орнатылмаған."</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын тарату"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Аудио шығысын өзгерту"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 080fc79..dc154ec 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ប្រសិនបើអ្នកផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹងបញ្ឈប់"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ការផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ប្ដូរឧបករណ៍បញ្ចេញសំឡេង"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index f6249a9..af063c6 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ನೀವು <xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿದರೆ ಅಥವಾ ಔಟ್ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರೆ, ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಪ್ರಸಾರವು ಸ್ಥಗಿತಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ಔಟ್ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿ"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"ಮುನ್ನೋಟದ ಬ್ಯಾಕ್ ಆ್ಯನಿಮೇಶನ್ಗಳು"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"ಮುನ್ನೋಟದ ಬ್ಯಾಕ್ ಗೆಸ್ಚರ್ಗಾಗಿ ಸಿಸ್ಟಂ ಆ್ಯನಿಮೇಶನ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ಮುನ್ನೋಟದ ಗೆಸ್ಚರ್ ಆ್ಯನಿಮೇಶನ್ಗಾಗಿ ಸಿಸ್ಟಂ ಆ್ಯನಿಮೇಶನ್ಗಳನ್ನು ಈ ಸೆಟ್ಟಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸುತ್ತವೆ. ಇದನ್ನು ಮಾಡಲು, ಪ್ರತಿ ಆ್ಯಪ್ನ ಮ್ಯಾನಿಫೆಸ್ಟ್ ಫೈಲ್ನಲ್ಲಿರುವ enableOnBackInvokedCallback ಪ್ಯಾರಾಮೀಟರ್ ಅನ್ನು ಸರಿ ಎಂದು ಹೊಂದಿಸಬೇಕು."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 53b4017..d22d048 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 방송"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"출력 변경"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 73830c5..a1f238f 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Колдонуучу"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Чектелген профайл"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Жаңы колдонуучу кошосузбу?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Эгер түзмөгүңүздү дагы бир адам колдонуп жаткан болсо, кошумча профилдерди түзүп коюңуз. Профилдин ээси аны өзү каалагандай жөндөп, тушкагаздарды коюп, керектүү колдонмолорду орнотуп алат. Мындан тышкары, колдонуучулар түзмөктүн Wi‑Fi´ды өчүрүү/күйгүзүү сыяктуу жалпы жөндөөлөрүн өзгөртө алат.\n\nПрофиль түзүлгөндөн кийин, аны жөндөп алуу керек.\n\nЖалпы колдонмолорду баары жаңырта алат, бирок атайын мүмкүнчүлүктөр өз-өзүнчө жөндөлөт."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Эгер түзмөгүңүздү дагы бир адам колдонуп жаткан болсо, кошумча профилдерди түзүп коюңуз. Профилдин ээси аны өзү каалагандай тууралап, тушкагаздарды коюп, керектүү колдонмолорду орнотуп алат. Мындан тышкары, колдонуучулар түзмөктүн Wi‑Fi´ды өчүрүү/күйгүзүү сыяктуу жалпы параметрлерин өзгөртө алышат.\n\nПрофиль түзүлгөндөн кийин, аны тууралап алуу керек.\n\nЖалпы колдонмолорду баары жаңырта алат, бирок атайын мүмкүнчүлүктөр өз-өзүнчө жөндөлөт."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Жаңы колдонуучу кошулганда, ал өз мейкиндигин түзүп алышы керек.\n\nКолдонмолорду бир колдонуучу жаңыртканда, ал калган бардык колдонуучулар үчүн да жаңырат."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Профилди жөндөйсүзбү?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Өз мейкиндигин жөндөп алышы үчүн, түзмөктү колдонуучуга беришиңиз керек."</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарлоо"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Аудионун чыгуусун өзгөртүү"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 46ce28d..f50f053 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ປ່ຽນເອົ້າພຸດ"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"ອະນິເມຊັນກັບຫຼັງແບບຄາດເດົາ"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"ເປີດການນຳໃຊ້ອະນິເມຊັນລະບົບສຳລັບການກັບຫຼັງແບບຄາດເດົາ."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ການຕັ້ງຄ່ານີ້ຈະເປີດການນຳໃຊ້ອະນິເມຊັນລະບົບສຳລັບອະນິເມຊັນທ່າທາງແບບຄາດເດົາ. ມັນຕ້ອງໃຊ້ການຕັ້ງຄ່າຕໍ່ແອັບ enableOnBackInvokedCallback ເປັນ true ໃນໄຟລ໌ manifest."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 250d24a..7229c0e 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transliuoti „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Keisti išvestį"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index f72ba8e..13020ac 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraide"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Izvades maiņa"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index ac91999..1a539c4 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Емитување на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Променете излез"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Предвидливи анимации отпозади"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Овозможете предвидливи системски анимации отпозади."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Поставкава ги овозможува системските анимации за предвидливи движења. Поставката треба да се постави на „точно“ преку апликација enableOnBackInvokedCallback во датотеката за манифест."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index ea0d9ee..697de2c 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"ഉപയോക്താവ്"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"നിയന്ത്രിത പ്രൊഫൈൽ"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"പുതിയ ഉപയോക്താവിനെ ചേർക്കണോ?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"കൂടുതൽ ഉപയോക്താക്കളെ സൃഷ്ടിച്ചുകൊണ്ട് ഈ ഉപകരണം മറ്റുള്ളവരുമായി നിങ്ങൾക്ക് പങ്കിടാം. ആപ്പുകളും വാൾപേപ്പറുകളും മറ്റും ഉപയോഗിച്ച് ഇഷ്ടാനുസൃതമാക്കാൻ ഓരോ ഉപയോക്താവിനും സാധിക്കും. വൈഫൈ പോലെ എല്ലാവരെയും ബാധിക്കുന്ന ഉപകരണ ക്രമീകരണവും ഉപയോക്താക്കൾക്ക് ക്രമീകരിക്കാം.\n\nനിങ്ങളൊരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിക്ക് സ്വന്തമായ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\n എല്ലാ ഉപയോക്താക്കൾക്കുമായി ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാൻ ഏതൊരു ഉപയോക്താവിനുമാവും. ഉപയോഗസഹായി ക്രമീകരണവും സേവനങ്ങളും പുതിയ ഉപയോക്താവിന് കൈമാറുകയില്ല."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"കൂടുതൽ ഉപയോക്താക്കളെ സൃഷ്ടിച്ചുകൊണ്ട് ഈ ഉപകരണം മറ്റുള്ളവരുമായി നിങ്ങൾക്ക് പങ്കിടാം. ആപ്പുകളും വാൾപേപ്പറുകളും മറ്റും ഉപയോഗിച്ച് ഇഷ്ടാനുസൃതമാക്കാൻ ഓരോ ഉപയോക്താവിനും സാധിക്കും. വൈഫൈ പോലെ എല്ലാവരെയും ബാധിക്കുന്ന ഉപകരണ ക്രമീകരണവും ഉപയോക്താക്കൾക്ക് അഡ്ജസ്റ്റ് ചെയ്യാം.\n\nനിങ്ങളൊരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തി സ്വന്തമായ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\n ഏതെങ്കിലും ഉപയോക്താവിന് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാനാകും. ഉപയോഗസഹായി ക്രമീകരണവും സേവനങ്ങളും പുതിയ ഉപയോക്താവിന് കൈമാറുകയില്ല."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"നിങ്ങൾ ഒരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിയ്ക്ക് അവരുടെ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\nമറ്റ് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ഏതൊരു ഉപയോക്താവിനും ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാനാവും."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ഉപയോക്താവിനെ ഇപ്പോൾ സജ്ജീകരിക്കണോ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ഉപകരണം എടുത്ത് ഇടം സജ്ജീകരിക്കുന്നതിന് വ്യക്തി ലഭ്യമാണെന്ന് ഉറപ്പാക്കുക"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്കാസ്റ്റ് അവസാനിക്കും"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുക"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ഔട്ട്പുട്ട് മാറ്റുക"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index cd41b81..d85eb91 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Хэрэглэгч"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Хязгаарлагдсан профайл"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Шинэ хэрэглэгч нэмэх үү?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Та нэмэлт хэрэглэгч үүсгэх замаар бусад хүмүүстэй энэ төхөөрөмжийг хуваалцаж болно. Хэрэглэгч тус бүр апп, дэлгэцийн зураг болон бусад зүйлээ өөрчлөх боломжтой хувийн орон зайтай байдаг. Түүнчлэн хэрэглэгч нь бүх хэрэглэгчид нөлөөлөх боломжтой Wi-Fi зэрэг төхөөрөмжийн тохиргоог өөрчлөх боломжтой.\n\nХэрэв та шинэ хэрэглэгч нэмэх бол тухайн хүн хувийн орон зайгаа бүрдүүлэх ёстой.\n\nХэрэглэгч бүр бусад бүх хэрэглэгчийн өмнөөс апп шинэчилж болно. Хүртээмжийн тохиргоо болон үйлчилгээг шинэ хэрэглэгчид шилжүүлэх боломжгүй байж болзошгүй."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Та нэмэлт хэрэглэгч үүсгэх замаар бусад хүмүүстэй энэ төхөөрөмжийг хуваалцаж болно. Хэрэглэгч тус бүр апп, дэлгэцийн зураг болон бусад зүйлээ өөрчлөх боломжтой хувийн орон зайтай байна. Түүнчлэн хэрэглэгч нь бүх хэрэглэгчид нөлөөлөх боломжтой Wi-Fi зэрэг төхөөрөмжийн тохиргоог өөрчлөх боломжтой.\n\nХэрэв та шинэ хэрэглэгч нэмэх бол тухайн хүн хувийн орон зайгаа бүрдүүлэх ёстой.\n\nХэрэглэгч бүр бусад бүх хэрэглэгчийн өмнөөс апп шинэчилж болно. Хандалтын тохиргоо болон үйлчилгээг шинэ хэрэглэгчид шилжүүлэх боломжгүй байж болзошгүй."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Та шинэ хэрэглэгч нэмбэл тухайн хүн өөрийн профайлыг тохируулах шаардлагатай.\n\nАль ч хэрэглэгч бүх хэрэглэгчийн апп-уудыг шинэчлэх боломжтой."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Хэрэглэгчийг одоо тохируулах уу?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Хэрэглэгч төхөөрөмжийг авч өөрийн профайлыг тохируулах боломжтой эсэхийг шалгана уу"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлэх"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Гаралтыг өөрчлөх"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Таамаглах боломжтой буцаах анимаци"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Таамаглах боломжтой буцаах зангаанд системийн анимацийг идэвхжүүлнэ үү."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Энэ тохиргоо нь таамаглах боломжтой зангааны анимацид системийн анимацийг идэвхжүүлнэ. Үүнд апп бүрд тодорхойлогч файлл enableOnBackInvokedCallback-г үнэн болгож тохируулахыг шаардана."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 5c3578c..0f07d30 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण करा"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपूट बदला"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"पूर्वानुमानित मागे जाण्याचे अॅनिमेशन"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"पूर्वानुमानित मागे जाण्यासाठीचे सिस्टीम अॅनिमेशन सुरू करा."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"हे सेटिंग पूर्वानुमानित जेश्चर ॲनिमेशनसाठी सिस्टीम ॲनिमेशन सुरू करते. यासाठी मॅनिफेस्ट फाइलमध्ये प्रति ॲप enableOnBackInvokedCallback सत्य वर सेट करणे आवश्यक आहे."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 8a2fc2b..ecee657 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jika anda siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau tukarkan output, siaran semasa anda akan berhenti"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Tukar output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animasi kembali ramalan"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Dayakan animasi sistem untuk kembali ramalan."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Tetapan ini mendayakan animasi sistem untuk animasi gerak isyarat ramalan. Animasi sistem memerlukan tetapan enableOnBackInvokedCallback untuk setiap apl kepada benar dalam fail manifes."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index c0aafc4..b241e8d 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -660,4 +660,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ထုတ်လွှင့်ခြင်း"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"အထွက်ကို ပြောင်းခြင်း"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 51f5260..df9e1df 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Kringkast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Endre utgang"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 4e86795..b476f65 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुहोस्"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपुट परिवर्तन गर्नुहोस्"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"पूर्वानुमानयुक्त ब्याक एनिमेसनहरू"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"पूर्वानुमानयुक्त ब्याक एनिमेसनका हकमा सिस्टम एनिमेसनहरू लागू गर्नुहोस्।"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"यो सेटिङले पूर्वानुमानयुक्त जेस्चर एनिमेसनका हकमा सिस्टम एनिमेनसहरू लागू गर्छ। म्यानिफेस्ट फाइलमा हरेक एपका हकमा enableOnBackInvokedCallback सेट गरी TRUE बनाएपछि मात्र यो सेटिङ अन गर्न मिल्छ।"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index c9cbbeb..2b7c117 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzenden"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Uitvoer wijzigen"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Voorspellende animaties voor gebaren voor terug"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Systeemanimaties aanzetten voor voorspellende animaties voor gebaren voor terug."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Met deze instelling zet je systeemanimaties aan voor voorspellende gebaaranimaties. Je moet enableOnBackInvokedCallback per app instellen op True in het manifestbestand."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index eb58dd2..400bf2c 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତୁ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ଆଉଟପୁଟ ବଦଳାନ୍ତୁ"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 6449971..0f71f97 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"ਵਰਤੋਂਕਾਰ"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"ਪ੍ਰਤਿਬੰਧਿਤ ਪ੍ਰੋਫਾਈਲ"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"ਕੀ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਨਾ ਹੈ?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"ਤੁਸੀਂ ਵਾਧੂ ਵਰਤੋਂਕਾਰ ਬਣਾ ਕੇ ਹੋਰਾਂ ਲੋਕਾਂ ਨਾਲ ਇਹ ਡੀਵਾਈਸ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹੋ। ਹਰੇਕ ਵਰਤੋਂਕਾਰ ਦੀ ਆਪਣੀ ਖੁਦ ਦੀ ਜਗ੍ਹਾ ਹੁੰਦੀ ਹੈ, ਜਿਸਨੂੰ ਉਹ ਐਪਾਂ ਅਤੇ ਵਾਲਪੇਪਰ ਆਦਿ ਨਾਲ ਵਿਉਂਤਬੱਧ ਕਰ ਸਕਦੇ ਹਨ। ਵਰਤੋਂਕਾਰ ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਵੀ ਵਿਵਸਥਿਤ ਕਰ ਸਕਦੇ ਹਨ ਜਿਵੇਂ ਵਾਈ‑ਫਾਈ ਜੋ ਹਰੇਕ \'ਤੇ ਅਸਰ ਪਾਉਂਦੀ ਹੈ।\n\nਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟ ਅੱਪ ਕਰਨੀ ਪੈਂਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਬਾਕੀ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ। ਸ਼ਾਇਦ ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਨੂੰ ਕਿਸੇ ਨਵੇਂ ਵਰਤੋਂਕਾਰ ਨੂੰ ਟ੍ਰਾਂਸਫਰ ਨਾ ਕੀਤਾ ਜਾ ਸਕੇ।"</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"ਤੁਸੀਂ ਵਾਧੂ ਵਰਤੋਂਕਾਰ ਬਣਾ ਕੇ ਹੋਰਾਂ ਲੋਕਾਂ ਨਾਲ ਇਹ ਡੀਵਾਈਸ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹੋ। ਹਰੇਕ ਵਰਤੋਂਕਾਰ ਦੀ ਆਪਣੀ ਖੁਦ ਦੀ ਜਗ੍ਹਾ ਹੁੰਦੀ ਹੈ, ਜਿਸਨੂੰ ਉਹ ਐਪਾਂ ਅਤੇ ਵਾਲਪੇਪਰ ਆਦਿ ਨਾਲ ਵਿਉਂਤਬੱਧ ਕਰ ਸਕਦੇ ਹਨ। ਵਰਤੋਂਕਾਰ ਵਾਈ-ਫਾਈ ਵਰਗੀਆਂ ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਨੂੰ ਵੀ ਵਿਵਸਥਿਤ ਕਰ ਸਕਦੇ ਹਨ, ਜਿਸ ਨਾਲ ਹਰੇਕ ਵਰਤੋਂਕਾਰ \'ਤੇ ਅਸਰ ਪੈਂਦਾ ਹੈ।\n\nਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟ ਅੱਪ ਕਰਨੀ ਪੈਂਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਬਾਕੀ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ। ਸ਼ਾਇਦ ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਨੂੰ ਕਿਸੇ ਨਵੇਂ ਵਰਤੋਂਕਾਰ ਨੂੰ ਟ੍ਰਾਂਸਫਰ ਨਾ ਕੀਤਾ ਜਾ ਸਕੇ।"</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟਅੱਪ ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਹੋਰ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ।"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ਕੀ ਹੁਣ ਵਰਤੋਂਕਾਰ ਸੈੱਟ ਅੱਪ ਕਰਨਾ ਹੈ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਵਿਅਕਤੀ ਡੀਵਾਈਸ ਵਰਤਣ ਅਤੇ ਆਪਣੀ ਜਗ੍ਹਾ ਦੇ ਸੈੱਟ ਅੱਪ ਲਈ ਉਪਲਬਧ ਹੈ"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰੋ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ਆਊਟਪੁੱਟ ਬਦਲੋ"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index b40308e..85fa056 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmisja aplikacji <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Zmień dane wyjściowe"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index a0355b9..b773b5c 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Mudar saída"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 5b41725..23b32785 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Utilizador"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Perfil restrito"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Adicionar novo utilizador?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Pode partilhar este dispositivo com outras pessoas ao criar utilizadores adicionais. Cada utilizador possui o seu próprio espaço, que pode ser personalizado com aplicações, imagens de fundo, etc. Os utilizadores também podem ajustar as definições do dispositivo, como o Wi‑Fi, que afetam os restantes utilizadores.\n\nAo adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar aplicações para todos os outros utilizadores. Os serviços e as definições de acessibilidade podem não ser transferidos para o novo utilizador."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Pode partilhar este dispositivo com outras pessoas ao criar utilizadores adicionais. Cada utilizador possui o seu próprio espaço, que pode ser personalizado com apps, imagens de fundo, etc. Os utilizadores também podem ajustar as definições do dispositivo, como o Wi‑Fi, que afetam os restantes utilizadores.\n\nAo adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar apps para todos os outros utilizadores. Os serviços e as definições de acessibilidade podem não ser transferidos para o novo utilizador."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Ao adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar aplicações para todos os outros utilizadores."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurar o utilizador agora?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Certifique-se de que a pessoa está disponível para levar o dispositivo e configurar o seu espaço"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmita a app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Altere a saída"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animações de gestos para voltar preditivos"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Ative as animações do sistema para gestos para voltar preditivos."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Esta definição ativa animações do sistema para a animação de gestos preditivos. Requer a definição do atributo enableOnBackInvokedCallback por app como verdadeiro no ficheiro de manifesto."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index a0355b9..b773b5c 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Mudar saída"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 5a773cf..d9627a4 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Dacă difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbați rezultatul, difuzarea actuală se va opri"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Schimbați rezultatul"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 3ce46e7..3e3693d 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Если вы начнете транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" или смените целевое устройство, текущая трансляция прервется."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Транслировать на другое устройство"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Анимации подсказки для жеста \"Назад\""</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Включить системную анимацию подсказки для жеста \"Назад\"."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"С помощью этого параметра можно включить системные анимации подсказок для жестов. Для этого нужно установить значение true для enableOnBackInvokedCallback в файле манифеста приложения."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 4e01d61..b3c8eff 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ප්රතිදානය වෙනස් කරන්න"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 143941d..6374cb7 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -280,7 +280,7 @@
<string name="wifi_display_certification" msgid="1805579519992520381">"Certifikácia bezdrôtového zobrazenia"</string>
<string name="wifi_verbose_logging" msgid="1785910450009679371">"Podrobné denníky Wi‑Fi"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Pribrzdiť vyhľadávanie sietí Wi‑Fi"</string>
- <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Randomizácia dočasnej adresy MAC siete Wi‑Fi"</string>
+ <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Randomizovať dočasnú adresu MAC siete Wi‑Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobilné dáta ponechať vždy aktívne"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Hardvérová akcelerácia tetheringu"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Zobrazovať zariadenia Bluetooth bez názvov"</string>
@@ -381,7 +381,7 @@
<string name="debug_layout_summary" msgid="8825829038287321978">"Zobraziť vo výstrižku ohraničenie, okraje a pod."</string>
<string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Rozloženie sprava doľava"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Vynútiť pre všetky jazyky rozloženie obrazovky sprava doľava"</string>
- <string name="window_blurs" msgid="6831008984828425106">"Povolenie rozmazania na úrovni okna"</string>
+ <string name="window_blurs" msgid="6831008984828425106">"Povoliť rozmazanie na úrovni okna"</string>
<string name="force_msaa" msgid="4081288296137775550">"Vynútiť 4x MSAA"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"Povoliť 4x MSAA v aplikáciách OpenGL ES 2.0"</string>
<string name="show_non_rect_clip" msgid="7499758654867881817">"Ladiť operácie s neobdĺžnikovými výstrižkami"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ak vysielate aplikáciu <xliff:g id="SWITCHAPP">%1$s</xliff:g> alebo zmeníte výstup, aktuálne vysielanie bude zastavené"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Vysielanie aplikácie <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Zmena výstupu"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Prediktívne animácie gesta Späť"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Povoľte animácie v systéme pre prediktívne gesto Späť"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Toto nastavenie povoľuje animácie v systéme na účely prediktívnej animácie gest. Vyžaduje nastavenie povolenia enableOnBackInvokedCallback na pravdu v súbore manifestu konkrétnej aplikácie."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 2ffa8bf..60c51dc 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -568,7 +568,7 @@
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"Dostop do aplikacij in vsebine iz vašega računa lahko omejite"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"Uporabnik"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Omejen profil"</string>
- <string name="user_add_user_title" msgid="5457079143694924885">"Dodajanje novega uporabnika?"</string>
+ <string name="user_add_user_title" msgid="5457079143694924885">"Želite dodati uporabnika?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"To napravo lahko delite z drugimi tako, da ustvarite dodatne uporabnike. Vsak ima svoj prostor, ki ga lahko prilagodi z aplikacijami, ozadji in drugim. Uporabniki lahko tudi prilagodijo nastavitve naprave, ki vplivajo na vse, na primer nastavitve omrežja Wi-Fi.\n\nKo dodate novega uporabnika, mora ta nastaviti svoj prostor.\n\nVsak uporabnik lahko posodobi aplikacije za vse druge uporabnike. Nastavitve in storitve funkcij za ljudi s posebnimi potrebami morda ne bodo prenesene v prostor novega uporabnika."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Ko dodate novega uporabnika, mora ta nastaviti svoj prostor.\n\nVsak uporabnik lahko posodobi aplikacije za vse druge uporabnike."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Želite uporabnika nastaviti zdaj?"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Oddajaj aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Sprememba izhoda"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije poteze za nazaj s predvidevanjem"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogoči sistemske animacije za potezo za nazaj s predvidevanjem."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ta nastavitev omogoča sistemske animacije za animacijo poteze s predvidevanjem. V datoteki manifesta mora biti parameter »enableOnBackInvokedCallback« za posamezno aplikacijo nastavljen na »true«."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 208da7a..e6ade6c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Nëse transmeton <xliff:g id="SWITCHAPP">%1$s</xliff:g> ose ndryshon daljen, transmetimi yt aktual do të ndalojë"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmeto <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ndrysho daljen"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index eba7910..c06e3f6 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -594,7 +594,7 @@
<string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Ресетуј сесију госта"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Желите ли да ресетујете сесију госта?"</string>
- <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Желите ли да уклоните госта?"</string>
+ <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Желите да уклоните госта?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Ресетуј"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Уклони"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Сесија госта се ресетује…"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако емитујете апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените излаз, актуелно емитовање ће се зауставити"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Емитујте апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Промените излаз"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Анимације за покрет повратка са предвиђањем"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Омогућите анимације система за покрет повратка са предвиђањем."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ово подешавање омогућава анимације система за покрет повратка са предвиђањем. Захтева подешавање дозволе enableOnBackInvokedCallback по апликацији на true у фајлу манифеста."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1c1c38f..0ca0348 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Användare"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Begränsad profil"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Lägga till ny användare?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dela enheten med andra om du skapar flera användare. Alla användare får sitt eget utrymme som de kan anpassa som de vill med appar, bakgrund och så vidare. Användarna kan även ändra enhetsinställningar som påverkar alla, till exempel Wi‑Fi.\n\nNär du lägger till en ny användare måste han eller hon konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning. Tillgänglighetsinställningar och tjänster kanske inte överförs till den nya användaren."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dela enheten med andra om du skapar flera användare. Alla användare får sitt eget utrymme som de kan anpassa som de vill med appar, bakgrund och så vidare. Användarna kan även ändra enhetsinställningar som påverkar alla, till exempel wifi.\n\nNär du lägger till en ny användare måste han eller hon konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning. Tillgänglighetsinställningar och tjänster kanske inte överförs till den nya användaren."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"När du lägger till en ny användare måste den personen konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Konfigurera användare nu?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Kontrollera att personen finns tillgänglig för att konfigurera sitt utrymme på enheten"</string>
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Sänd från <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Byt ljudutgång"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 3280e4f..9775c25 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ikiwa unatangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g> au unabadilisha maudhui, tangazo lako la sasa litasimamishwa"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Tangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Badilisha maudhui"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 236b74b..a041cb8 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பு"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"அவுட்புட்டை மாற்று"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"கணிக்கக்கூடிய பின்செல் சைகைக்கான அனிமேஷன்கள்"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"கணிக்கக்கூடிய பின்செல் சைகைக்காகச் சிஸ்டம் அனிமேஷன்களை இயக்கும்."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"கணிக்கக்கூடிய சைகைக்கான அனிமேஷனுக்காக இந்த அமைப்பு சிஸ்டம் அனிமேஷன்களை இயக்கும். மெனிஃபெஸ்ட் ஃபைலில் ஒவ்வொரு ஆப்ஸுக்கும் enableOnBackInvokedCallbackகை \'சரி\' என அமைக்க வேண்டும்."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c25966c..83bbeb6 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -583,7 +583,7 @@
<string name="profile_info_settings_title" msgid="105699672534365099">"ప్రొఫైల్ సమాచారం"</string>
<string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్ను క్రియేట్ చేయడానికి ముందు, మీ యాప్లు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్ను సెటప్ చేయాల్సి ఉంటుంది."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"లాక్ను సెట్ చేయి"</string>
- <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు స్విచ్ చేయి"</string>
+ <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు స్విచ్ చేయి"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string>
<string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"కొత్త అతిథిని క్రియేట్ చేస్తోంది…"</string>
<string name="add_user_failed" msgid="4809887794313944872">"కొత్త యూజర్ను క్రియేట్ చేయడం విఫలమైంది"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్పుట్ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేయండి"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"అవుట్పుట్ను మార్చండి"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"ఊహించదగిన బ్యాక్ యానిమేషన్లు"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"ఊహించదగిన బ్యాక్ యానిమేషన్ల కోసం సిస్టమ్ యానిమేషన్లను ఎనేబుల్ చేయండి."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ఊహించదగిన సంజ్ఞ యానిమేషన్ కోసం ఈ సెట్టింగ్ సిస్టమ్ యానిమేషన్లను ఎనేబుల్ చేస్తుంది. దీనికి మ్యానిఫెస్ట్ ఫైల్లో ఒక్కో యాప్లో enableOnBackInvokedCallback సెట్టింగ్ను ఒప్పునకు సెట్ చేయవలసి ఉంటుంది."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f31fde6..848c8f1 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -569,7 +569,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"ผู้ใช้"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"โปรไฟล์ที่ถูกจำกัด"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"ต้องการเพิ่มผู้ใช้ใหม่ใช่ไหม"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"คุณมีสิทธิ์แชร์อุปกรณ์นี้กับผู้อื่นได้โดยการเพิ่มผู้ใช้ ซึ่งแต่ละคนจะมีพื้นที่ของตนเองและปรับใช้กับแอป วอลเปเปอร์ และรายการอื่นๆ ได้ อีกทั้งยังปรับการตั้งค่าอุปกรณ์ได้ด้วย เช่น Wi‑Fi ซึ่งจะมีผลกับทุกคน\n\nเมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตน\n\nผู้ใช้ทุกคนมีสิทธิ์อัปเดตแอปให้กับผู้ใช้รายอื่น การตั้งค่าและบริการสำหรับการช่วยเหลือพิเศษอาจโอนไปยังผู้ใช้ใหม่ไม่ได้"</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"คุณมีสิทธิ์แชร์อุปกรณ์นี้กับผู้อื่นได้โดยการเพิ่มผู้ใช้ แต่ละคนจะมีพื้นที่ของตนเองซึ่งปรับใช้กับแอป วอลเปเปอร์ และรายการอื่นๆ ได้ อีกทั้งยังปรับการตั้งค่าอุปกรณ์ได้ด้วย เช่น Wi‑Fi ซึ่งจะมีผลกับทุกคน\n\nเมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตน\n\nผู้ใช้ทุกคนมีสิทธิ์อัปเดตแอปให้ผู้ใช้รายอื่น การตั้งค่าและบริการสำหรับการช่วยเหลือพิเศษอาจโอนไปยังผู้ใช้ใหม่ไม่ได้"</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"เมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตนเอง\n\nผู้ใช้ทุกคนสามารถอัปเดตแอปสำหรับผู้ใช้รายอื่นได้"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ตั้งค่าผู้ใช้เลยไหม"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ตรวจสอบว่าบุคคลดังกล่าวสามารถนำอุปกรณ์ไปตั้งค่าพื้นที่ของตนได้"</string>
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"เปลี่ยนเอาต์พุต"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"การเคลื่อนไหวย้อนกลับแบบคาดเดา"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"เปิดใช้การเคลื่อนไหวของระบบสำหรับท่าทางสัมผัสย้อนกลับแบบคาดเดา"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"การตั้งค่านี้จะเปิดใช้การเคลื่อนไหวของระบบสำหรับการเคลื่อนไหวจากท่าทางสัมผัสแบบคาดเดา โดยต้องตั้งค่า enableOnBackInvokedCallback สำหรับแต่ละแอปให้เป็น \"จริง\" ในไฟล์ Manifest"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 7d4a4b4..40f8435 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"I-broadcast ang <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Baguhin ang output"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Mga animation ng predictive na pagbalik"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"I-enable ang mga animation ng system para sa predictive na pagbalik."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ine-enable ng setting na ito ang mga animation ng system para sa animation ng predictive na galaw. Kinakailangan nitong itakda sa true ang enableOnBackInvokedCallback sa bawat app sa manifest file."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 15c9cac..b81c1ac 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapın"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Çıkışı değiştirme"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 0cbbae8..07f3d82 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Змінити додаток для трансляції на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Змінити аудіовихід"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 3056ef7..59b9fc9 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"اگر آپ <xliff:g id="SWITCHAPP">%1$s</xliff:g> براڈکاسٹ کرتے ہیں یا آؤٹ پٹ کو تبدیل کرتے ہیں تو آپ کا موجودہ براڈکاسٹ رک جائے گا"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> پر براڈکاسٹ کریں"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"آؤٹ پٹ تبدیل کریں"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"پیچھے جانے کے اشارے کی پیش گوئی والی اینیمیشنز"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"پیچھے جانے کے پیش گوئی والے اشارے کے لیے سسٹم اینیمیشنز فعال کریں۔"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"یہ ترتیب پیش گوئی والی اشارے کی اینیمیشن کے لیے سسٹم کی اینیمیشنز کو فعال کرتی ہے۔ اس کے لیے manifest فائل میں فی ایپ enableOnBackInvokedCallback کو درست پر سیٹ کرنے کی ضرورت ہے۔"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 348e795..29f7f97 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Agar <xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya qilsangiz yoki ovoz chiqishini oʻzgartirsangiz, joriy translatsiya toʻxtab qoladi"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ovoz chiqishini oʻzgartirish"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Taxminiy qaytish animatsiyalari"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Taxminiy qaytish uchun tizim animatsiyalarini yoqish."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Bu sozlamalar taxminiy qaytish animatsiyalari uchun tizim animatsiyalarini faollashtiradi. Buning uchun har bir ilovaning manifest faylida enableOnBackInvokedCallback parametri “true” qiymatida boʻlishi lozim."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index ea5230c..4cf8ff4 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -178,13 +178,13 @@
<item msgid="2983219471251787208">"8 MB/vùng đệm nhật ký"</item>
</string-array>
<string-array name="select_logpersist_titles">
- <item msgid="704720725704372366">"Tắt"</item>
+ <item msgid="704720725704372366">"Đang tắt"</item>
<item msgid="6014837961827347618">"Tất cả"</item>
<item msgid="7387060437894578132">"Tất cả trừ đài"</item>
<item msgid="7300881231043255746">"chỉ kernel"</item>
</string-array>
<string-array name="select_logpersist_summaries">
- <item msgid="97587758561106269">"Tắt"</item>
+ <item msgid="97587758561106269">"Đang tắt"</item>
<item msgid="7126170197336963369">"Tất cả lần tải nhật ký"</item>
<item msgid="7167543126036181392">"Tất cả trừ lần tải nhật ký qua đài"</item>
<item msgid="5135340178556563979">"chỉ vùng đệm nhật ký kernel"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5c4932f..98388bd 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Phát <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Thay đổi đầu ra"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Ảnh động vuốt ngược dự đoán"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Cho phép sử dụng ảnh động hệ thống cho chức năng vuốt ngược dự đoán."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Cài đặt này cho phép sử dụng ảnh động hệ thống cho ảnh động cử chỉ dự đoán. Nó yêu cầu cài đặt cho mỗi ứng dụng chuyển enableOnBackInvokedCallback thành lệnh true trong tệp kê khai."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 9bb725a..121a24a 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -653,10 +653,13 @@
<string name="allow_turn_screen_on" msgid="6194845766392742639">"允许开启屏幕"</string>
<string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用开启屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时开启屏幕。"</string>
<string name="bt_le_audio_scan_qr_code" msgid="3521809854780392679">"扫描二维码"</string>
- <string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"将扫描器对准下方二维码,即可开始收听"</string>
+ <string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"将取景框对准二维码,即可开始收听"</string>
<string name="bt_le_audio_qr_code_is_not_valid_format" msgid="6092191081849434734">"二维码的格式无效"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"更改输出来源"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"预测性返回手势动画"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"启用系统动画作为预测性返回手势动画。"</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"此设置将启用系统动画作为预测性手势动画。这要求在清单文件中将单个应用的 enableOnBackInvokedCallback 设为 true。"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 8a581b7..4f20f68 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"變更輸出來源"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index f77e889..dc7f9e7 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -659,4 +659,10 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"變更輸出來源"</string>
+ <!-- no translation found for back_navigation_animation (8105467568421689484) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_summary (741292224121599456) -->
+ <skip />
+ <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 1cb1fa4..bc8c6fe 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -659,4 +659,7 @@
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Sakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Shintsha okuphumayo"</string>
+ <string name="back_navigation_animation" msgid="8105467568421689484">"Ukubikezelwa kwasemuva kopopayi"</string>
+ <string name="back_navigation_animation_summary" msgid="741292224121599456">"Nika amandla ukubikezela emuva kopopayi besistimu."</string>
+ <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Leli sethingi livumela opopayi besistimu mayelana nokuthinta okubikezelwayo kopopayi. Idinga ukusetha i-app ngayinye ku-enableOnBackInvokedCallback ukuze iqinisekise ifayela le-manifest."</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 284da73..2f30baa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -234,5 +234,10 @@
if (!(mPreference instanceof RestrictedTopLevelPreference)) {
mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
}
+
+ if (mPreference instanceof PrimarySwitchPreference) {
+ ((PrimarySwitchPreference) mPreference)
+ .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index cc4fef8..7f65837 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -268,9 +268,9 @@
/**
* Preload the top N icons of app entry list.
*
- * @param context caller's context
+ * @param context caller's context
* @param appEntries AppEntry list of ApplicationsState
- * @param number the number of Top N icons of the appEntries
+ * @param number the number of Top N icons of the appEntries
*/
public static void preloadTopIcons(Context context,
ArrayList<ApplicationsState.AppEntry> appEntries, int number) {
@@ -286,6 +286,19 @@
}
}
+ /**
+ * Returns a boolean indicating whether this app is installed or not.
+ *
+ * @param appEntry AppEntry of ApplicationsState.
+ * @return true if the app is in installed state.
+ */
+ public static boolean isAppInstalled(ApplicationsState.AppEntry appEntry) {
+ if (appEntry == null || appEntry.info == null) {
+ return false;
+ }
+ return (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
+ }
+
private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) {
if (appEntry.mounted != mounted) {
synchronized (appEntry) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fdb0607..6b9daa3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -863,6 +863,30 @@
}
}
+ /**
+ * Activate session to enable a class that implements Callbacks to receive the callback.
+ */
+ public void activateSession() {
+ synchronized (mEntriesMap) {
+ if (!mResumed) {
+ mResumed = true;
+ mSessionsChanged = true;
+ }
+ }
+ }
+
+ /**
+ * Deactivate session to disable a class that implements Callbacks to get the callback.
+ */
+ public void deactivateSession() {
+ synchronized (mEntriesMap) {
+ if (mResumed) {
+ mResumed = false;
+ mSessionsChanged = true;
+ }
+ }
+ }
+
public ArrayList<AppEntry> getAllApps() {
synchronized (mEntriesMap) {
return new ArrayList<>(mAppEntries);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 4ee2122..6919cf2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1376,7 +1376,7 @@
/**
* Store the member devices that are in the same coordinated set.
*/
- public void setMemberDevice(CachedBluetoothDevice memberDevice) {
+ public void addMemberDevice(CachedBluetoothDevice memberDevice) {
mMemberDevices.add(memberDevice);
}
@@ -1393,24 +1393,24 @@
* device and member devices.
*
* @param prevMainDevice the previous Main device, it will be added into the member device set.
- * @param newMainDevie the new Main device, it will be removed from the member device set.
+ * @param newMainDevice the new Main device, it will be removed from the member device set.
*/
public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice,
- CachedBluetoothDevice newMainDevie) {
+ CachedBluetoothDevice newMainDevice) {
// Backup from main device
final BluetoothDevice tmpDevice = mDevice;
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
// Set main device from sub device
- mDevice = newMainDevie.mDevice;
- mRssi = newMainDevie.mRssi;
- mJustDiscovered = newMainDevie.mJustDiscovered;
- setMemberDevice(prevMainDevice);
- mMemberDevices.remove(newMainDevie);
+ mDevice = newMainDevice.mDevice;
+ mRssi = newMainDevice.mRssi;
+ mJustDiscovered = newMainDevice.mJustDiscovered;
+ addMemberDevice(prevMainDevice);
+ mMemberDevices.remove(newMainDevice);
// Set sub device from backup
- newMainDevie.mDevice = tmpDevice;
- newMainDevie.mRssi = tmpRssi;
- newMainDevie.mJustDiscovered = tmpJustDiscovered;
+ newMainDevice.mDevice = tmpDevice;
+ newMainDevice.mRssi = tmpRssi;
+ newMainDevice.mJustDiscovered = tmpJustDiscovered;
fetchActiveDevices();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index cc56a21..89e10c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -85,7 +85,7 @@
// Once there is other devices with the same groupId, to add new device as member
// devices.
if (CsipDevice != null) {
- CsipDevice.setMemberDevice(newDevice);
+ CsipDevice.addMemberDevice(newDevice);
newDevice.setName(CsipDevice.getName());
return true;
}
@@ -148,7 +148,7 @@
log("onGroupIdChanged: removed from UI device =" + cachedDevice
+ ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
- mainDevice.setMemberDevice(cachedDevice);
+ mainDevice.addMemberDevice(cachedDevice);
mCachedDevices.remove(i);
mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
break;
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 8a1e91b..3e7481a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -147,6 +147,7 @@
mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
mAdapter.mSelectedPosition =
savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+ mDoneButton.setEnabled(mAdapter.mSelectedPosition != AvatarAdapter.NONE);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 80ee86f..3b542cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -54,6 +54,7 @@
private Dialog mEditUserInfoDialog;
private Bitmap mSavedPhoto;
+ private Drawable mSavedDrawable;
private EditUserPhotoController mEditUserPhotoController;
private boolean mWaitingForActivityResult = false;
private final String mFileAuthority;
@@ -68,6 +69,7 @@
}
mEditUserInfoDialog = null;
mSavedPhoto = null;
+ mSavedDrawable = null;
}
/**
@@ -170,7 +172,8 @@
private Drawable getUserIcon(Activity activity, Drawable defaultUserIcon) {
if (mSavedPhoto != null) {
- return CircleFramedDrawable.getInstance(activity, mSavedPhoto);
+ mSavedDrawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
+ return mSavedDrawable;
}
return defaultUserIcon;
}
@@ -229,6 +232,6 @@
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mFileAuthority);
+ mSavedPhoto, mSavedDrawable, mFileAuthority);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 5862f60..38cf383 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,7 +62,7 @@
private Drawable mNewUserPhotoDrawable;
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
- ImageView view, Bitmap bitmap, String fileAuthority) {
+ ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
mActivity = activity;
mActivityStarter = activityStarter;
mFileAuthority = fileAuthority;
@@ -71,7 +71,9 @@
mImagesDir.mkdir();
mImageView = view;
mImageView.setOnClickListener(v -> showAvatarPicker());
- mNewUserPhotoBitmap = bitmap;
+
+ mNewUserPhotoBitmap = savedBitmap;
+ mNewUserPhotoDrawable = savedDrawable;
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 8e448aa..994c1ee 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -129,6 +129,28 @@
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
}
+ @Test
+ public void isAppInstalled_noAppEntry_shouldReturnFalse() {
+ assertThat(AppUtils.isAppInstalled(null)).isFalse();
+ }
+
+ @Test
+ public void isAppInstalled_hasAppEntryWithInstalledFlag_shouldReturnTrue() {
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = new ApplicationInfo();
+ appEntry.info.flags = ApplicationInfo.FLAG_INSTALLED;
+
+ assertThat(AppUtils.isAppInstalled(appEntry)).isTrue();
+ }
+
+ @Test
+ public void isAppInstalled_hasAppEntryWithoutInstalledFlag_shouldReturnFalse() {
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = new ApplicationInfo();
+
+ assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
+ }
+
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 298ee90..bef1d9c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -40,7 +40,6 @@
import org.robolectric.RuntimeEnvironment;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
@RunWith(RobolectricTestRunner.class)
@@ -503,8 +502,8 @@
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- cachedDevice1.setMemberDevice(cachedDevice2);
- cachedDevice1.setMemberDevice(cachedDevice3);
+ cachedDevice1.addMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice3);
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
@@ -524,7 +523,7 @@
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
cachedDevice1.setGroupId(1);
cachedDevice2.setGroupId(1);
- cachedDevice1.setMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice2);
// Call onDeviceUnpaired for the one in mCachedDevices.
mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
@@ -541,7 +540,7 @@
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
cachedDevice1.setGroupId(1);
cachedDevice2.setGroupId(1);
- cachedDevice1.setMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice2);
// Call onDeviceUnpaired for the one in mCachedDevices.
mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 61a28aa..9abb27e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.view.LayoutInflater;
+import android.view.View;
import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
@@ -61,7 +62,7 @@
mFooterPreference.onBindViewHolder(holder);
assertThat(((TextView) holder.findViewById(
- R.id.settingslib_learn_more)).getText().toString())
+ R.id.settingslib_learn_more)).getText().toString())
.isEqualTo("Custom learn more");
}
@@ -86,4 +87,11 @@
assertThat(mFooterPreference.mLearnMoreListener).isNotNull();
}
+
+ @Test
+ public void setIconVisibility_shouldReturnSameVisibilityType() {
+ mFooterPreference.setIconVisibility(View.GONE);
+
+ assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3029781..5eaf553 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -120,6 +120,9 @@
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a4da497..9ee7b65 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -18,6 +18,7 @@
import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
@@ -176,6 +177,10 @@
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 0c6d40aa..5088533 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1074,13 +1074,63 @@
if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration ");
// Depending on device hardware, we may need to notify the user of a setting change
SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
- if (!storedConfig.equals(configInCloud)) {
+
+ if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
}
}
}
+ private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+ SoftApConfiguration storedConfig) {
+ // Check if the cloud configuration was modified when restored to the device.
+ // All elements of the configuration are compared except:
+ // 1. Persistent randomized MAC address (which is per device)
+ // 2. The flag indicating whether the configuration is "user modified"
+ return !(Objects.equals(configInCloud.getWifiSsid(), storedConfig.getWifiSsid())
+ && Objects.equals(configInCloud.getBssid(), storedConfig.getBssid())
+ && Objects.equals(configInCloud.getPassphrase(), storedConfig.getPassphrase())
+ && configInCloud.isHiddenSsid() == storedConfig.isHiddenSsid()
+ && configInCloud.getChannels().toString().equals(
+ storedConfig.getChannels().toString())
+ && configInCloud.getSecurityType() == storedConfig.getSecurityType()
+ && configInCloud.getMaxNumberOfClients() == storedConfig.getMaxNumberOfClients()
+ && configInCloud.isAutoShutdownEnabled() == storedConfig.isAutoShutdownEnabled()
+ && configInCloud.getShutdownTimeoutMillis()
+ == storedConfig.getShutdownTimeoutMillis()
+ && configInCloud.isClientControlByUserEnabled()
+ == storedConfig.isClientControlByUserEnabled()
+ && Objects.equals(configInCloud.getBlockedClientList(),
+ storedConfig.getBlockedClientList())
+ && Objects.equals(configInCloud.getAllowedClientList(),
+ storedConfig.getAllowedClientList())
+ && configInCloud.getMacRandomizationSetting()
+ == storedConfig.getMacRandomizationSetting()
+ && configInCloud.isBridgedModeOpportunisticShutdownEnabled()
+ == storedConfig.isBridgedModeOpportunisticShutdownEnabled()
+ && configInCloud.isIeee80211axEnabled() == storedConfig.isIeee80211axEnabled()
+ && configInCloud.isIeee80211beEnabled() == storedConfig.isIeee80211beEnabled()
+ && configInCloud.getBridgedModeOpportunisticShutdownTimeoutMillis()
+ == storedConfig.getBridgedModeOpportunisticShutdownTimeoutMillis()
+ && Objects.equals(configInCloud.getVendorElements(),
+ storedConfig.getVendorElements())
+ && (configInCloud.getPersistentRandomizedMacAddress() != null
+ ? Objects.equals(configInCloud.getPersistentRandomizedMacAddress(),
+ storedConfig.getPersistentRandomizedMacAddress()) : true)
+ && Arrays.equals(configInCloud.getAllowedAcsChannels(
+ SoftApConfiguration.BAND_2GHZ),
+ storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ))
+ && Arrays.equals(configInCloud.getAllowedAcsChannels(
+ SoftApConfiguration.BAND_5GHZ),
+ storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ))
+ && Arrays.equals(configInCloud.getAllowedAcsChannels(
+ SoftApConfiguration.BAND_6GHZ),
+ storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ))
+ && configInCloud.getMaxChannelBandwidth() == storedConfig.getMaxChannelBandwidth()
+ );
+ }
+
private byte[] getNetworkPolicies() {
NetworkPolicyManager networkPolicyManager =
(NetworkPolicyManager) getSystemService(NETWORK_POLICY_SERVICE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index aadfcea..a6edb0f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5509,16 +5509,7 @@
currentVersion = 209;
}
if (currentVersion == 209) {
- // Version 209: Enable enforcement of
- // android.Manifest.permission#POST_NOTIFICATIONS in order for applications
- // to post notifications.
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(
- Secure.NOTIFICATION_PERMISSION_ENABLED,
- /* enabled= */ "1",
- /* tag= */ null,
- /* makeDefault= */ false,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ // removed now that feature is enabled for everyone
currentVersion = 210;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 290ce34..4d0888a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -323,6 +323,8 @@
<!-- To read safety center status -->
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <uses-permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume.xml b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
new file mode 100644
index 0000000..fce4e00
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@color/media_dialog_item_main_content"
+ android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.125,8.475 15.812,9.575Q16.5,10.675 16.5,12Q16.5,13.325 15.812,14.4Q15.125,15.475 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index 528f603..fd06238 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ 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.
@@ -15,88 +14,83 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.core.widget.NestedScrollView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <com.android.internal.widget.AlertDialogLayout
- android:id="@*android:id/parentPanel"
+<com.android.internal.widget.AlertDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@*android:id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/dialog_top_padding"
+>
+
+ <include layout="@layout/alert_dialog_title_systemui" />
+
+ <FrameLayout
+ android:id="@*android:id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_horizontal|top"
- android:orientation="vertical"
- android:paddingTop="@dimen/dialog_top_padding"
- >
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
- <include layout="@layout/alert_dialog_title_systemui" />
-
- <FrameLayout
- android:id="@*android:id/contentPanel"
+ <ScrollView
+ android:id="@*android:id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding"
- >
+ android:clipToPadding="false">
- <ScrollView
- android:id="@*android:id/scrollView"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipToPadding="false">
+ android:orientation="vertical">
- <LinearLayout
+ <Space
+ android:id="@*android:id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
+
+ <TextView
+ android:id="@*android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ style="@style/TextAppearance.Dialog.Body.Message" />
- <Space
- android:id="@*android:id/textSpacerNoTitle"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="0dp" />
+ <Space
+ android:id="@*android:id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="6dp" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
- <TextView
- android:id="@*android:id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.Dialog.Body.Message" />
-
- <Space
- android:id="@*android:id/textSpacerNoButtons"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="6dp" />
- </LinearLayout>
- </ScrollView>
- </FrameLayout>
+ <FrameLayout
+ android:id="@*android:id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
<FrameLayout
- android:id="@*android:id/customPanel"
+ android:id="@*android:id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding">
+ <include
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding"
- >
-
- <FrameLayout
- android:id="@*android:id/custom"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </FrameLayout>
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding">
- <include
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- layout="@layout/alert_dialog_button_bar_systemui" />
- </FrameLayout>
- </com.android.internal.widget.AlertDialogLayout>
-
-</androidx.core.widget.NestedScrollView>
\ No newline at end of file
+ layout="@layout/alert_dialog_button_bar_systemui" />
+ </FrameLayout>
+</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index b230438..085a581 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -67,7 +67,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="@dimen/overlay_offset_y"
+ android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
android:elevation="7dp"
@@ -121,6 +121,30 @@
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/hidden_text_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <TextView
+ android:id="@+id/hidden_image_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="#ffffff"
+ android:background="#000000"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss_button"
@@ -141,4 +165,4 @@
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 890dbe5..c29e11b 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -29,18 +29,11 @@
android:clickable="true"
android:importantForAccessibility="no"/>
<ImageView
- android:id="@+id/screenshot_actions_background"
- android:layout_height="@dimen/overlay_bg_protection_height"
- android:layout_width="match_parent"
- android:layout_gravity="bottom"
- android:alpha="0.0"
- android:src="@drawable/overlay_actions_background_protection"/>
- <ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
- android:elevation="@dimen/overlay_preview_elevation"
+ android:elevation="7dp"
android:src="@android:color/white"/>
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/screenshot_selector"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index c60609b..9c02749 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -24,7 +24,7 @@
android:visibility="gone"
android:layout_height="0dp"
android:layout_width="0dp"
- android:elevation="1dp"
+ android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/actions_container"
@@ -36,9 +36,10 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="4dp"
android:paddingEnd="@dimen/overlay_action_container_padding_right"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="1dp"
+ android:elevation="4dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
@@ -64,8 +65,8 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="@dimen/overlay_offset_y"
- android:elevation="@dimen/overlay_preview_elevation"
+ android:layout_marginBottom="12dp"
+ android:elevation="7dp"
android:alpha="0"
android:background="@drawable/overlay_border"
app:layout_constraintStart_toStartOf="parent"
@@ -93,7 +94,7 @@
android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:elevation="@dimen/overlay_preview_elevation"
+ android:elevation="7dp"
android:contentDescription="@string/screenshot_edit_description"
android:scaleType="fitEnd"
android:background="@drawable/overlay_preview_background"
@@ -108,7 +109,7 @@
android:id="@+id/screenshot_dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="@dimen/overlay_dismiss_button_elevation"
+ android:elevation="10dp"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
@@ -130,5 +131,5 @@
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- android:elevation="@dimen/overlay_preview_elevation"/>
+ android:elevation="7dp"/>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml
new file mode 100644
index 0000000..71bb938
--- /dev/null
+++ b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<androidx.core.widget.NestedScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/alert_dialog_systemui" />
+
+</androidx.core.widget.NestedScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 1d6f279..e6af6f4 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">38dp</dimen>
+ <dimen name="keyguard_clock_top_margin">26dp</dimen>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/defaults.xml b/packages/SystemUI/res/values/defaults.xml
deleted file mode 100644
index f96c178..0000000
--- a/packages/SystemUI/res/values/defaults.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2009, 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.
- */
--->
-<resources>
- <!-- Default for SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER.
- To be set if the device wants to support out of the box QR code scanning experience -->
- <string name="def_qr_code_component" translatable="false"></string>
-</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a014efb..0bc3594 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -282,15 +282,12 @@
<!-- Spacing between chip icon and chip text -->
<dimen name="overlay_action_chip_spacing">8dp</dimen>
<dimen name="overlay_action_chip_text_size">14sp</dimen>
- <dimen name="overlay_offset_y">8dp</dimen>
<dimen name="overlay_offset_x">16dp</dimen>
- <dimen name="overlay_preview_elevation">4dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">18dp</dimen>
<dimen name="overlay_action_container_padding_vertical">4dp</dimen>
<dimen name="overlay_action_container_padding_right">8dp</dimen>
- <dimen name="overlay_dismiss_button_elevation">7dp</dimen>
<dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
<dimen name="overlay_dismiss_button_margin">8dp</dimen>
<dimen name="overlay_border_width">4dp</dimen>
@@ -1134,7 +1131,7 @@
<!-- Output switcher panel related dimensions -->
<dimen name="media_output_dialog_list_margin">12dp</dimen>
- <dimen name="media_output_dialog_list_max_height">364dp</dimen>
+ <dimen name="media_output_dialog_list_max_height">355dp</dimen>
<dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
<dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d2645e8..2426f01 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2482,6 +2482,8 @@
<string name="clipboard_edit_image_description">Edit copied image</string>
<!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
<string name="clipboard_send_nearby_description">Send to nearby device</string>
+ <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] -->
+ <string name="clipboard_text_hidden">Tap to view</string>
<!-- Generic "add" string [CHAR LIMIT=NONE] -->
<string name="add">Add</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f9e73ec..0c25f54 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -380,7 +380,7 @@
<item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
<item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
<item name="android:colorBackground">?androidprv:attr/colorSurface</item>
- <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+ <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item>
<item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
<item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
</style>
@@ -389,6 +389,10 @@
<item name="android:layout">@layout/alert_dialog_systemui</item>
</style>
+ <style name="ScrollableAlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
+ <item name="android:layout">@layout/scrollable_alert_dialog_systemui</item>
+ </style>
+
<style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog">
<item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item>
<item name="android:paddingBottom">@dimen/dialog_bottom_padding</item>
@@ -994,6 +998,7 @@
<style name="Theme.SystemUI.Dialog.Cast">
<item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item>
+ <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
</style>
<!-- ************************************************************************************* -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 6345d11..1d6a3bf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -92,9 +92,18 @@
}
};
+ /**
+ * @deprecated Pass a main executor.
+ */
public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
Executor backgroundExecutor) {
this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(),
+ backgroundExecutor);
+ }
+
+ public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
+ Executor mainExecutor, Executor backgroundExecutor) {
+ this(sampledView, samplingCallback, mainExecutor,
backgroundExecutor, new SysuiCompositionSamplingListener());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index f195d20..38fa354 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -16,14 +16,20 @@
package com.android.keyguard
+import android.annotation.IntDef
import android.content.ContentResolver
import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
+import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -44,6 +50,20 @@
dumpManager: DumpManager
) : Dumpable {
+ companion object {
+ const val TAG = "ActiveUnlockConfig"
+
+ const val BIOMETRIC_TYPE_NONE = 0
+ const val BIOMETRIC_TYPE_ANY_FACE = 1
+ const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
+ const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
+ BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
+ annotation class BiometricType
+
/**
* Indicates the origin for an active unlock request.
*/
@@ -51,35 +71,50 @@
WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
}
+ var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
private var requestActiveUnlockOnWakeup = false
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
+ private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
+ private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
+ private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
+
private val settingsObserver = object : ContentObserver(handler) {
- private val wakeUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
- private val unlockIntentUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
- private val bioFailUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+ private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
+ private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
+ private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+ private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)
+ private val faceAcquireInfoUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
+ private val unlockIntentWhenBiometricEnrolledUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
fun register() {
- contentResolver.registerContentObserver(
- wakeUri,
- false,
- this,
- UserHandle.USER_ALL)
- contentResolver.registerContentObserver(
- unlockIntentUri,
- false,
- this,
- UserHandle.USER_ALL)
- contentResolver.registerContentObserver(
- bioFailUri,
- false,
- this,
- UserHandle.USER_ALL)
+ registerUri(
+ listOf(
+ wakeUri,
+ unlockIntentUri,
+ bioFailUri,
+ faceErrorsUri,
+ faceAcquireInfoUri,
+ unlockIntentWhenBiometricEnrolledUri
+ )
+ )
onChange(true, ArrayList(), 0, getCurrentUser())
}
+ private fun registerUri(uris: Collection<Uri>) {
+ for (uri in uris) {
+ contentResolver.registerContentObserver(
+ uri,
+ false,
+ this,
+ UserHandle.USER_ALL)
+ }
+ }
+
override fun onChange(
selfChange: Boolean,
uris: Collection<Uri>,
@@ -104,6 +139,55 @@
requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
}
+
+ if (selfChange || uris.contains(faceErrorsUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ getCurrentUser()),
+ faceErrorsToTriggerBiometricFailOn,
+ setOf(FACE_ERROR_TIMEOUT))
+ }
+
+ if (selfChange || uris.contains(faceAcquireInfoUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ getCurrentUser()),
+ faceAcquireInfoToTriggerBiometricFailOn,
+ setOf<Int>())
+ }
+
+ if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ getCurrentUser()),
+ onUnlockIntentWhenBiometricEnrolled,
+ setOf(BIOMETRIC_TYPE_NONE))
+ }
+ }
+
+ /**
+ * Convert a pipe-separated set of integers into a set of ints.
+ * @param stringSetting expected input are integers delineated by a pipe. For example,
+ * it may look something like this: "1|5|3".
+ * @param out updates the "out" Set will the integers between the pipes.
+ * @param default If stringSetting is null, "out" will be populated with values in "default"
+ */
+ private fun processStringArray(
+ stringSetting: String?,
+ out: MutableSet<Int>,
+ default: Set<Int>
+ ) {
+ out.clear()
+ stringSetting?.let {
+ for (code: String in stringSetting.split("|")) {
+ try {
+ out.add(code.toInt())
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, "Passed an invalid setting=$code")
+ }
+ }
+ } ?: out.addAll(default)
}
}
@@ -113,6 +197,30 @@
}
/**
+ * If any active unlock triggers are enabled.
+ */
+ fun isActiveUnlockEnabled(): Boolean {
+ return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
+ requestActiveUnlockOnBioFail
+ }
+
+ /**
+ * Whether the face error code from {@link BiometricFaceConstants} should trigger
+ * active unlock on biometric failure.
+ */
+ fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean {
+ return faceErrorsToTriggerBiometricFailOn.contains(errorCode)
+ }
+
+ /**
+ * Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger
+ * active unlock on biometric failure.
+ */
+ fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean {
+ return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo)
+ }
+
+ /**
* Whether to trigger active unlock based on where the request is coming from and
* the current settings.
*/
@@ -121,7 +229,8 @@
ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
- requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup
+ requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
+ (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
@@ -131,17 +240,55 @@
}
}
- /**
- * If any active unlock triggers are enabled.
- */
- fun isActiveUnlockEnabled(): Boolean {
- return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
- requestActiveUnlockOnBioFail
+ private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean {
+ if (!requestActiveUnlockOnBioFail) {
+ return false
+ }
+
+ keyguardUpdateMonitor?.let {
+ val anyFaceEnrolled = it.isFaceEnrolled
+ val anyFingerprintEnrolled =
+ it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+ val udfpsEnrolled = it.isUdfpsEnrolled
+
+ if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
+ }
+
+ if (!anyFaceEnrolled && anyFingerprintEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(
+ BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
+ (udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
+ BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
+ }
+
+ if (!anyFingerprintEnrolled && anyFaceEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
+ }
+ }
+
+ return false
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Settings:")
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
+ "$onUnlockIntentWhenBiometricEnrolled")
+ pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
+ pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
+ "$faceAcquireInfoToTriggerBiometricFailOn")
+
+ pw.println("Current state:")
+ keyguardUpdateMonitor?.let {
+ pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
+ "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
+ pw.println(" faceEnrolled=${it.isFaceEnrolled}")
+ pw.println(" fpEnrolled=${
+ it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+ pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}")
+ } ?: pw.println(" keyguardUpdateMonitor is uninitialized")
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bbe9a362..e0f1b65 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -56,7 +56,6 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -189,6 +188,7 @@
private static final int MSG_KEYGUARD_GOING_AWAY = 342;
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
+ private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346;
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -1615,7 +1615,7 @@
mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
}
- if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
requestActiveUnlock(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
"faceError-" + errMsgId);
@@ -1625,6 +1625,13 @@
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFaceAcquired(acquireInfo);
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ acquireInfo)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ "faceAcquireInfo-" + acquireInfo);
+ }
}
};
@@ -1639,6 +1646,7 @@
private boolean mFingerprintLockedOut;
private boolean mFingerprintLockedOutPermanent;
private boolean mFaceLockedOutPermanent;
+ private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
private TelephonyManager mTelephonyManager;
/**
@@ -1889,6 +1897,7 @@
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mActiveUnlockConfig = activeUnlockConfiguration;
+ mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mHandler = new Handler(mainLooper) {
@Override
@@ -1997,6 +2006,9 @@
case MSG_REQUIRE_NFC_UNLOCK:
handleRequireUnlockForNfc();
break;
+ case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED:
+ handleKeyguardDismissAnimationFinished();
+ break;
default:
super.handleMessage(msg);
break;
@@ -2329,7 +2341,7 @@
}
if (shouldTriggerActiveUnlock()) {
- if (DEBUG) {
+ if (DEBUG_ACTIVE_UNLOCK) {
Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
}
mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
@@ -2359,7 +2371,7 @@
}
if (allowRequest && shouldTriggerActiveUnlock()) {
- if (DEBUG) {
+ if (DEBUG_ACTIVE_UNLOCK) {
Log.d("ActiveUnlock", "reportUserRequestedUnlock"
+ " origin=" + requestOrigin.name()
+ " reason=" + reason
@@ -2777,8 +2789,17 @@
}
private boolean isUnlockWithFingerprintPossible(int userId) {
- return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
- && mFpm.hasEnrolledTemplates(userId);
+ mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
+ && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
+ return mIsUnlockWithFingerprintPossible.get(userId);
+ }
+
+ /**
+ * Cached value for whether fingerprint is enrolled and possible to use for authentication.
+ * Note: checking fingerprint enrollment directly with the AuthController requires an IPC.
+ */
+ public boolean getCachedIsUnlockWithFingerprintPossible(int userId) {
+ return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
}
private boolean isUnlockWithFacePossible(int userId) {
@@ -3260,6 +3281,19 @@
}
/**
+ * Handle {@link #MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED}
+ */
+ private void handleKeyguardDismissAnimationFinished() {
+ Assert.isMainThread();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardDismissAnimationFinished();
+ }
+ }
+ }
+
+ /**
* Handle {@link #MSG_REPORT_EMERGENCY_CALL_ACTION}
*/
private void handleReportEmergencyCallAction() {
@@ -3597,6 +3631,13 @@
mHandler.sendMessage(mHandler.obtainMessage(MSG_KEYGUARD_GOING_AWAY, goingAway));
}
+ /**
+ * Sends a message to notify the keyguard dismiss animation is finished.
+ */
+ public void dispatchKeyguardDismissAnimationFinished() {
+ mHandler.sendEmptyMessage(MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED);
+ }
+
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 2620195..051b81e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -109,6 +109,14 @@
public void onKeyguardBouncerFullyShowingChanged(boolean bouncerIsFullyShowing) { }
/**
+ * Called when the dismissing animation of keyguard and surfaces behind is finished.
+ * If the surface behind is the Launcher, we may still be playing in-window animations
+ * when this is called (since it's only called once we dismiss the keyguard and end the
+ * remote animation).
+ */
+ public void onKeyguardDismissAnimationFinished() { }
+
+ /**
* Called when visibility of lockscreen clock changes, such as when
* obscured by a widget.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index e191365..fdde402 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -55,7 +55,7 @@
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
- private int mRadius;
+ private float mRadius;
private int mLockIconPadding;
private ImageView mLockIcon;
@@ -126,7 +126,7 @@
* Set the location of the lock icon.
*/
@VisibleForTesting
- public void setCenterLocation(@NonNull PointF center, int radius, int drawablePadding) {
+ public void setCenterLocation(@NonNull PointF center, float radius, int drawablePadding) {
mLockIconCenter = center;
mRadius = radius;
mLockIconPadding = drawablePadding;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2b217f0..d79b145 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -188,6 +188,7 @@
protected void onViewAttached() {
updateIsUdfpsEnrolled();
updateConfiguration();
+ updateLockIconLocation();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
@@ -340,20 +341,17 @@
mHeightPixels = bounds.bottom;
mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
- final int defaultPaddingPx =
- getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
- mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
-
mUnlockedLabel = mView.getContext().getResources().getString(
R.string.accessibility_unlock_button);
mLockedLabel = mView.getContext()
.getResources().getString(R.string.accessibility_lock_icon);
-
- updateLockIconLocation();
}
private void updateLockIconLocation() {
if (mUdfpsSupported) {
+ final int defaultPaddingPx =
+ getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
+ mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
mView.setCenterLocation(mAuthController.getUdfpsLocation(),
mAuthController.getUdfpsRadius(), mScaledPaddingPx);
} else {
@@ -362,8 +360,6 @@
mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx),
sLockIconRadiusPx, mScaledPaddingPx);
}
-
- mView.getHitRect(mSensorTouchLocation);
}
@Override
@@ -386,6 +382,7 @@
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+ pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
if (mView != null) {
mView.dump(pw, args);
@@ -672,6 +669,7 @@
}
private boolean inLockIconArea(MotionEvent event) {
+ mView.getHitRect(mSensorTouchLocation);
return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& mView.getVisibility() == View.VISIBLE;
}
@@ -692,6 +690,7 @@
mExecutor.execute(() -> {
updateIsUdfpsEnrolled();
updateConfiguration();
+ updateLockIconLocation();
});
}
@@ -705,6 +704,11 @@
public void onEnrollmentsChanged() {
updateUdfpsConfig();
}
+
+ @Override
+ public void onUdfpsLocationChanged() {
+ updateLockIconLocation();
+ }
};
private final View.OnClickListener mA11yClickListener = v -> onLongPress();
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index e51a63f..032a27a 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -54,7 +54,7 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@JvmField val displayInfo = DisplayInfo()
- @JvmField protected var pendingRotationChange = false
+ @JvmField protected var pendingConfigChange = false
@JvmField protected val paint = Paint()
@JvmField protected val cutoutPath = Path()
@@ -145,7 +145,7 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
open fun updateCutout() {
- if (pendingRotationChange) {
+ if (pendingConfigChange) {
return
}
cutoutPath.reset()
@@ -225,7 +225,7 @@
}
protected open fun updateProtectionBoundingPath() {
- if (pendingRotationChange) {
+ if (pendingConfigChange) {
return
}
val m = Matrix()
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index cbce854..685c585 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -47,11 +47,13 @@
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Handler;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.util.DisplayUtils;
import android.util.Log;
import android.util.Size;
+import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayCutout.BoundsPosition;
import android.view.DisplayInfo;
@@ -150,12 +152,13 @@
private SettingObserver mColorInversionSetting;
private DelayableExecutor mExecutor;
private Handler mHandler;
- boolean mPendingRotationChange;
+ boolean mPendingConfigChange;
@VisibleForTesting
String mDisplayUniqueId;
private int mTintColor = Color.BLACK;
@VisibleForTesting
protected DisplayDecorationSupport mHwcScreenDecorationSupport;
+ private Display.Mode mDisplayMode;
private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -323,6 +326,7 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mRotation = mContext.getDisplay().getRotation();
+ mDisplayMode = mContext.getDisplay().getMode();
mDisplayUniqueId = mContext.getDisplay().getUniqueId();
mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
mDisplayUniqueId);
@@ -348,8 +352,10 @@
@Override
public void onDisplayChanged(int displayId) {
final int newRotation = mContext.getDisplay().getRotation();
+ final Display.Mode newDisplayMode = mContext.getDisplay().getMode();
if ((mOverlays != null || mScreenDecorHwcWindow != null)
- && mRotation != newRotation) {
+ && (mRotation != newRotation
+ || displayModeChanged(mDisplayMode, newDisplayMode))) {
// We cannot immediately update the orientation. Otherwise
// WindowManager is still deferring layout until it has finished dispatching
// the config changes, which may cause divergence between what we draw
@@ -357,10 +363,16 @@
// Instead we wait until either:
// - we are trying to redraw. This because WM resized our window and told us to.
// - the config change has been dispatched, so WM is no longer deferring layout.
- mPendingRotationChange = true;
+ mPendingConfigChange = true;
if (DEBUG) {
- Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
- + mRotation);
+ if (mRotation != newRotation) {
+ Log.i(TAG, "Rotation changed, deferring " + newRotation
+ + ", staying at " + mRotation);
+ }
+ if (displayModeChanged(mDisplayMode, newDisplayMode)) {
+ Log.i(TAG, "Resolution changed, deferring " + newDisplayMode
+ + ", staying at " + mDisplayMode);
+ }
}
if (mOverlays != null) {
@@ -368,7 +380,8 @@
if (mOverlays[i] != null) {
final ViewGroup overlayView = mOverlays[i].getRootView();
overlayView.getViewTreeObserver().addOnPreDrawListener(
- new RestartingPreDrawListener(overlayView, i, newRotation));
+ new RestartingPreDrawListener(
+ overlayView, i, newRotation, newDisplayMode));
}
}
}
@@ -378,7 +391,10 @@
new RestartingPreDrawListener(
mScreenDecorHwcWindow,
-1, // Pass -1 for views with no specific position.
- newRotation));
+ newRotation, newDisplayMode));
+ }
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.pendingConfigChange = true;
}
}
@@ -434,7 +450,7 @@
};
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
- updateOrientation();
+ updateConfiguration();
}
@Nullable
@@ -806,6 +822,17 @@
}
}
+ private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
+ if (oldMode == null) {
+ return true;
+ }
+
+ // We purposely ignore refresh rate and id changes here, because we don't need to
+ // invalidate for those, and they can trigger the refresh rate to increase
+ return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
+ || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+ }
+
private int getOverlayWindowGravity(@BoundsPosition int pos) {
final int rotated = getBoundPositionFromRotation(pos, mRotation);
switch (rotated) {
@@ -912,8 +939,8 @@
mExecutor.execute(() -> {
int oldRotation = mRotation;
- mPendingRotationChange = false;
- updateOrientation();
+ mPendingConfigChange = false;
+ updateConfiguration();
if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
setupDecorations();
if (mOverlays != null) {
@@ -940,7 +967,7 @@
pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
pw.println(" isOnlyPrivacyDotInSwLayer:" + isOnlyPrivacyDotInSwLayer());
- pw.println(" mPendingRotationChange:" + mPendingRotationChange);
+ pw.println(" mPendingConfigChange:" + mPendingConfigChange);
if (mHwcScreenDecorationSupport != null) {
pw.println(" mHwcScreenDecorationSupport:");
pw.println(" format="
@@ -956,15 +983,23 @@
} else {
pw.println(" mScreenDecorHwcLayer: null");
}
- pw.println(" mOverlays(left,top,right,bottom)=("
- + (mOverlays != null && mOverlays[BOUNDS_POSITION_LEFT] != null) + ","
- + (mOverlays != null && mOverlays[BOUNDS_POSITION_TOP] != null) + ","
- + (mOverlays != null && mOverlays[BOUNDS_POSITION_RIGHT] != null) + ","
- + (mOverlays != null && mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")");
+ if (mOverlays != null) {
+ pw.println(" mOverlays(left,top,right,bottom)=("
+ + (mOverlays[BOUNDS_POSITION_LEFT] != null) + ","
+ + (mOverlays[BOUNDS_POSITION_TOP] != null) + ","
+ + (mOverlays[BOUNDS_POSITION_RIGHT] != null) + ","
+ + (mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")");
+
+ for (int i = BOUNDS_POSITION_LEFT; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] != null) {
+ mOverlays[i].dump(pw, getWindowTitleByPos(i));
+ }
+ }
+ }
mRoundedCornerResDelegate.dump(pw, args);
}
- private void updateOrientation() {
+ private void updateConfiguration() {
Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
"must call on " + mHandler.getLooper().getThread()
+ ", but was " + Thread.currentThread());
@@ -973,11 +1008,14 @@
if (mRotation != newRotation) {
mDotViewController.setNewRotation(newRotation);
}
+ final Display.Mode newMod = mContext.getDisplay().getMode();
- if (!mPendingRotationChange && newRotation != mRotation) {
+ if (!mPendingConfigChange
+ && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
mRotation = newRotation;
+ mDisplayMode = newMod;
if (mScreenDecorHwcLayer != null) {
- mScreenDecorHwcLayer.pendingRotationChange = false;
+ mScreenDecorHwcLayer.pendingConfigChange = false;
mScreenDecorHwcLayer.updateRotation(mRotation);
updateHwLayerRoundedCornerExistAndSize();
updateHwLayerRoundedCornerDrawable();
@@ -1067,15 +1105,22 @@
}
private void updateLayoutParams() {
- if (mOverlays == null) {
- return;
+ //ToDo: We should skip unnecessary call to update view layout.
+ Trace.beginSection("ScreenDecorations#updateLayoutParams");
+ if (mScreenDecorHwcWindow != null) {
+ mWindowManager.updateViewLayout(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
}
- for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- if (mOverlays[i] == null) {
- continue;
+
+ if (mOverlays != null) {
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] == null) {
+ continue;
+ }
+ mWindowManager.updateViewLayout(
+ mOverlays[i].getRootView(), getWindowLayoutParams(i));
}
- mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i));
}
+ Trace.endSection();
}
@Override
@@ -1181,7 +1226,7 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@Override
public void updateCutout() {
- if (!isAttachedToWindow() || pendingRotationChange) {
+ if (!isAttachedToWindow() || pendingConfigChange) {
return;
}
mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation);
@@ -1322,40 +1367,47 @@
private final View mView;
private final int mTargetRotation;
+ private final Display.Mode mTargetDisplayMode;
// Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
// position.
private final int mPosition;
private RestartingPreDrawListener(View view, @BoundsPosition int position,
- int targetRotation) {
+ int targetRotation, Display.Mode targetDisplayMode) {
mView = view;
mTargetRotation = targetRotation;
+ mTargetDisplayMode = targetDisplayMode;
mPosition = position;
}
@Override
public boolean onPreDraw() {
mView.getViewTreeObserver().removeOnPreDrawListener(this);
-
- if (mTargetRotation == mRotation) {
+ if (mTargetRotation == mRotation
+ && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
if (DEBUG) {
final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
: getWindowTitleByPos(mPosition);
Log.i(TAG, title + " already in target rot "
- + mTargetRotation + ", allow draw without restarting it");
+ + mTargetRotation + " and in target resolution "
+ + mTargetDisplayMode.getPhysicalWidth() + "x"
+ + mTargetDisplayMode.getPhysicalHeight()
+ + ", allow draw without restarting it");
}
return true;
}
- mPendingRotationChange = false;
+ mPendingConfigChange = false;
// This changes the window attributes - we need to restart the traversal for them to
// take effect.
- updateOrientation();
+ updateConfiguration();
if (DEBUG) {
final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
: getWindowTitleByPos(mPosition);
Log.i(TAG, title
- + " restarting listener fired, restarting draw for rot " + mRotation);
+ + " restarting listener fired, restarting draw for rot " + mRotation
+ + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
+ + mDisplayMode.getPhysicalHeight());
}
mView.invalidate();
return false;
@@ -1363,8 +1415,8 @@
}
/**
- * A pre-draw listener, that validates that the rotation we draw in matches the displays
- * rotation before continuing the draw.
+ * A pre-draw listener, that validates that the rotation and display resolution we draw in
+ * matches the display's rotation and resolution before continuing the draw.
*
* This is to prevent a race condition, where we have not received the display changed event
* yet, and would thus draw in an old orientation.
@@ -1380,10 +1432,20 @@
@Override
public boolean onPreDraw() {
final int displayRotation = mContext.getDisplay().getRotation();
- if (displayRotation != mRotation && !mPendingRotationChange) {
+ final Display.Mode displayMode = mContext.getDisplay().getMode();
+ if (displayRotation != mRotation && displayModeChanged(mDisplayMode, displayMode)
+ && !mPendingConfigChange) {
if (DEBUG) {
- Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
- + displayRotation + ". Restarting draw");
+ if (displayRotation != mRotation) {
+ Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
+ + displayRotation + ". Restarting draw");
+ }
+ if (displayModeChanged(mDisplayMode, displayMode)) {
+ Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
+ + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
+ + displayMode.getPhysicalWidth() + "x"
+ + displayMode.getPhysicalHeight() + ". Restarting draw");
+ }
}
mView.invalidate();
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 56fbe6d..6b85976 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -102,6 +102,7 @@
AppOpsManager.OP_PHONE_CALL_CAMERA,
AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
AppOpsManager.OP_PHONE_CALL_MICROPHONE,
AppOpsManager.OP_COARSE_LOCATION,
AppOpsManager.OP_FINE_LOCATION
@@ -375,7 +376,7 @@
Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName,
Boolean.toString(active), attributionChainId, attributionFlags));
}
- if (attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE
+ if (active && attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE
&& attributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE
&& (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR) == 0
&& (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_TRUSTED) == 0) {
@@ -535,7 +536,8 @@
}
private boolean isOpMicrophone(int op) {
- return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+ return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE
+ || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
}
protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 9324893..75339aa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -79,6 +79,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
@@ -446,11 +447,11 @@
/**
* @return the radius of UDFPS on the screen in pixels
*/
- public int getUdfpsRadius() {
+ public float getUdfpsRadius() {
if (mUdfpsController == null || mUdfpsBounds == null) {
return -1;
}
- return mUdfpsBounds.height() / 2;
+ return mUdfpsBounds.height() / 2f;
}
/**
@@ -634,11 +635,17 @@
displayInfo.getNaturalHeight());
final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+ final Rect previousUdfpsBounds = mUdfpsBounds;
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(scaleFactor);
mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
new UdfpsOverlayParams(mUdfpsBounds, displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation));
+ if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
+ for (Callback cb : mCallbacks) {
+ cb.onUdfpsLocationChanged();
+ }
+ }
}
}
@@ -1054,5 +1061,10 @@
* Called when the biometric prompt is no longer showing.
*/
default void onBiometricPromptDismissed() {}
+
+ /**
+ * The location in pixels can change due to resolution changes.
+ */
+ default void onUdfpsLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index d9aa1ba..86e5016 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -335,15 +335,17 @@
updateSensorLocation()
}
- override fun onEnrollmentsChanged() {
+ override fun onUdfpsLocationChanged() {
+ updateUdfpsDependentParams()
+ updateSensorLocation()
}
}
private fun updateUdfpsDependentParams() {
authController.udfpsProps?.let {
if (it.size > 0) {
- udfpsRadius = it[0].location.sensorRadius.toFloat()
udfpsController = udfpsControllerProvider.get()
+ udfpsRadius = authController.udfpsRadius
if (mView.isAttachedToWindow) {
udfpsController?.addCallback(udfpsControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 4cd40d2..2035781 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,11 @@
*/
package com.android.systemui.biometrics
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
/**
@@ -28,6 +30,7 @@
statusBarStateController: StatusBarStateController,
panelExpansionStateManager: PanelExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
+ val broadcastSender: BroadcastSender,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
@@ -37,4 +40,29 @@
dumpManager
) {
override val tag = "UdfpsBpViewController"
+ private val bpPanelExpansionListener = PanelExpansionListener { event ->
+ // Notification shade can be expanded but not visible (fraction: 0.0), for example
+ // when a heads-up notification (HUN) is showing.
+ notificationShadeVisible = event.expanded && event.fraction > 0f
+ view.onExpansionChanged(event.fraction)
+ cancelAuth()
+ }
+
+ fun cancelAuth() {
+ if (shouldPauseAuth()) {
+ broadcastSender.closeSystemDialogs()
+ }
+ }
+
+ override fun onViewAttached() {
+ super.onViewAttached()
+
+ panelExpansionStateManager.addExpansionListener(bpPanelExpansionListener)
+ }
+
+ override fun onViewDetached() {
+ super.onViewDetached()
+
+ panelExpansionStateManager.removeExpansionListener(bpPanelExpansionListener)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a35f042..7657269 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -54,6 +54,7 @@
import com.android.keyguard.ActiveUnlockConfig;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
@@ -125,6 +126,7 @@
@NonNull private final UnlockedScreenOffAnimationController
mUnlockedScreenOffAnimationController;
@NonNull private final LatencyTracker mLatencyTracker;
+ @NonNull private final BroadcastSender mBroadcastSender;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -205,7 +207,7 @@
mUnlockedScreenOffAnimationController, mHalControlsIllumination,
mHbmProvider, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ fromUdfpsView), mActivityLaunchAnimator, mBroadcastSender)));
}
@Override
@@ -574,7 +576,8 @@
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
- @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider) {
+ @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider,
+ @NonNull BroadcastSender broadcastSender) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -604,6 +607,7 @@
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = aternateTouchProvider.orElse(null);
+ mBroadcastSender = broadcastSender;
mOrientationListener = new BiometricDisplayListener(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 2d51c97..faa93a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -41,6 +41,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -83,7 +84,8 @@
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val broadcastSender: BroadcastSender
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -102,8 +104,8 @@
fitInsetsTypes = 0
gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- flags =
- (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+ flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS
+ or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
@@ -221,6 +223,7 @@
statusBarStateController,
panelExpansionStateManager,
dialogManager,
+ broadcastSender,
dumpManager
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 2f09792..8de7213 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -154,7 +154,9 @@
//re-calculate the height of description
View description = mView.findViewById(R.id.description);
- totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ if (description != null && description.getVisibility() != View.GONE) {
+ totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ }
return new AuthDialog.LayoutParams(width, totalHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 9139699..f28fedb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -32,6 +32,7 @@
import android.view.ViewGroup;
import android.widget.ImageView;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
@@ -44,6 +45,8 @@
import com.airbnb.lottie.model.KeyPath;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* View corresponding with udfps_keyguard_view.xml
@@ -52,7 +55,6 @@
private UdfpsDrawable mFingerprintDrawable; // placeholder
private LottieAnimationView mAodFp;
private LottieAnimationView mLockScreenFp;
- private int mStatusBarState;
// used when highlighting fp icon:
private int mTextColorPrimary;
@@ -70,7 +72,7 @@
private float mBurnInOffsetY;
private float mBurnInProgress;
private float mInterpolatedDarkAmount;
- private boolean mAnimatingBetweenAodAndLockscreen; // As opposed to Unlocked => AOD
+ private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
@@ -117,8 +119,10 @@
return;
}
- final float darkAmountForAnimation = mAnimatingBetweenAodAndLockscreen
- ? mInterpolatedDarkAmount : 1f /* animating from unlocked to AOD */;
+ // if we're animating from screen off, we can immediately place the icon in the
+ // AoD-burn in location, else we need to translate the icon from LS => AoD.
+ final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
+ ? 1f : mInterpolatedDarkAmount;
mBurnInOffsetX = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
- mMaxBurnInOffsetX, darkAmountForAnimation);
@@ -127,12 +131,12 @@
- mMaxBurnInOffsetY, darkAmountForAnimation);
mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation);
- if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) {
+ if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) {
mLockScreenFp.setTranslationX(mBurnInOffsetX);
mLockScreenFp.setTranslationY(mBurnInOffsetY);
mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
- } else if (mInterpolatedDarkAmount == 0f) {
+ } else if (darkAmountForAnimation == 0f) {
mLockScreenFp.setTranslationX(0);
mLockScreenFp.setTranslationY(0);
mBgProtection.setAlpha(mAlpha / 255f);
@@ -148,9 +152,15 @@
mAodFp.setProgress(mBurnInProgress);
mAodFp.setAlpha(mInterpolatedDarkAmount);
- // done animating between AoD & LS
- if (mInterpolatedDarkAmount == 1f || mInterpolatedDarkAmount == 0f) {
- mAnimatingBetweenAodAndLockscreen = false;
+ // done animating
+ final boolean doneAnimatingBetweenAodAndLS =
+ mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+ && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f);
+ final boolean doneAnimatingUnlockedScreenOff =
+ mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
+ && (mInterpolatedDarkAmount == 1f);
+ if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) {
+ mAnimationType = ANIMATION_NONE;
}
}
@@ -158,10 +168,6 @@
mUdfpsRequested = request;
}
- void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
- }
-
void updateColor() {
if (!mFullyInflated) {
return;
@@ -219,8 +225,16 @@
return mAlpha;
}
- void onDozeAmountChanged(float linear, float eased, boolean animatingBetweenAodAndLockscreen) {
- mAnimatingBetweenAodAndLockscreen = animatingBetweenAodAndLockscreen;
+ static final int ANIMATION_NONE = 0;
+ static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1;
+ static final int ANIMATION_UNLOCKED_SCREEN_OFF = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATION_UNLOCKED_SCREEN_OFF})
+ private @interface AnimationType {}
+
+ void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) {
+ mAnimationType = animationType;
mInterpolatedDarkAmount = eased;
updateAlpha();
}
@@ -262,7 +276,7 @@
pw.println(" mUnpausedAlpha=" + getUnpausedAlpha());
pw.println(" mUdfpsRequested=" + mUdfpsRequested);
pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
- pw.println(" mAnimatingBetweenAodAndLockscreen=" + mAnimatingBetweenAodAndLockscreen);
+ pw.println(" mAnimationType=" + mAnimationType);
}
private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8b0f36f..ec4cf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -121,7 +121,7 @@
mView.onDozeAmountChanged(
animation.getAnimatedFraction(),
(float) animation.getAnimatedValue(),
- /* animatingBetweenAodAndLockScreen */ false);
+ UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
}
});
}
@@ -394,7 +394,7 @@
mUnlockedScreenOffDozeAnimator.start();
} else {
mView.onDozeAmountChanged(linear, eased,
- /* animatingBetweenAodAndLockScreen */ true);
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
}
mLastDozeAmount = linear;
@@ -404,7 +404,6 @@
@Override
public void onStateChanged(int statusBarState) {
mStatusBarState = statusBarState;
- mView.setStatusBarState(statusBarState);
updateAlpha();
updatePauseAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 6a9317f..f526277 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -20,10 +20,14 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
+import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
@@ -37,6 +41,13 @@
@SysUISingleton
public class ClipboardListener extends CoreStartable
implements ClipboardManager.OnPrimaryClipChangedListener {
+ private static final String TAG = "ClipboardListener";
+
+ @VisibleForTesting
+ static final String SHELL_PACKAGE = "com.android.shell";
+ @VisibleForTesting
+ static final String EXTRA_SUPPRESS_OVERLAY =
+ "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
private final DeviceConfigProxy mDeviceConfig;
private final ClipboardOverlayControllerFactory mOverlayFactory;
@@ -68,18 +79,44 @@
if (!mClipboardManager.hasPrimaryClip()) {
return;
}
+
String clipSource = mClipboardManager.getPrimaryClipSource();
+ ClipData clipData = mClipboardManager.getPrimaryClip();
+
+ if (shouldSuppressOverlay(clipData, clipSource, isEmulator())) {
+ Log.i(TAG, "Clipboard overlay suppressed.");
+ return;
+ }
+
if (mClipboardOverlayController == null) {
mClipboardOverlayController = mOverlayFactory.create(mContext);
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlayController.setClipData(
- mClipboardManager.getPrimaryClip(), clipSource);
+ mClipboardOverlayController.setClipData(clipData, clipSource);
mClipboardOverlayController.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlayController = null;
});
}
+
+ // The overlay is suppressed if EXTRA_SUPPRESS_OVERLAY is true and the device is an emulator or
+ // the source package is SHELL_PACKAGE. This is meant to suppress the overlay when the emulator
+ // or a mirrored device is syncing the clipboard.
+ @VisibleForTesting
+ static boolean shouldSuppressOverlay(ClipData clipData, String clipSource,
+ boolean isEmulator) {
+ if (!(isEmulator || SHELL_PACKAGE.equals(clipSource))) {
+ return false;
+ }
+ if (clipData == null || clipData.getDescription().getExtras() == null) {
+ return false;
+ }
+ return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false);
+ }
+
+ private static boolean isEmulator() {
+ return SystemProperties.getBoolean("ro.boot.qemu", false);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 726d00c..5055056 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -35,15 +35,19 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.MainThread;
+import android.app.ICompatCameraControlCallback;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -67,6 +71,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -84,6 +89,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
+import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
@@ -128,6 +134,8 @@
private final View mClipboardPreview;
private final ImageView mImagePreview;
private final TextView mTextPreview;
+ private final TextView mHiddenTextPreview;
+ private final TextView mHiddenImagePreview;
private final View mPreviewBorder;
private final OverlayActionChip mEditChip;
private final OverlayActionChip mRemoteCopyChip;
@@ -147,6 +155,10 @@
private boolean mBlockAttach = false;
private Animator mExitAnimator;
+ /** Tracks config changes that require updating insets */
+ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+ ActivityInfo.CONFIG_KEYBOARD_HIDDEN);
+
public ClipboardOverlayController(Context context,
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
@@ -186,6 +198,8 @@
mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mHiddenTextPreview = requireNonNull(mView.findViewById(R.id.hidden_text_preview));
+ mHiddenImagePreview = requireNonNull(mView.findViewById(R.id.hidden_image_preview));
mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
@@ -227,6 +241,24 @@
mWindow.setContentView(mView);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
mView.requestLayout();
+ mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ }
+ }
+
+ @Override
+ public void requestCompatCameraControl(
+ boolean showControl, boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "unexpected requestCompatCameraControl call");
+ }
+ });
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
@@ -270,21 +302,32 @@
mExitAnimator.cancel();
}
reset();
+
+ boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+ && clipData.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(mContext.getResources().getString(
- R.string.clipboard_overlay_text_copied));
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (item.getTextLinks() != null) {
AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
}
- showEditableText(item.getText());
+ if (isSensitive) {
+ showEditableText(
+ mContext.getResources().getString(R.string.clipboard_text_hidden), true);
+ } else {
+ showEditableText(item.getText(), false);
+ }
} else if (clipData.getItemAt(0).getUri() != null) {
// How to handle non-image URIs?
- showEditableImage(clipData.getItemAt(0).getUri());
+ showEditableImage(clipData.getItemAt(0).getUri(), isSensitive);
} else {
showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
}
Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
// Only show remote copy if it's available.
@@ -406,15 +449,23 @@
animateOut();
}
- private void showTextPreview(CharSequence text) {
- mTextPreview.setVisibility(View.VISIBLE);
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
- mTextPreview.setText(text.subSequence(0, Math.min(500, text.length())));
+ mHiddenTextPreview.setVisibility(View.GONE);
+ mHiddenImagePreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private void showTextPreview(CharSequence text, TextView textView) {
+ showSinglePreview(textView);
+ textView.setText(text.subSequence(0, Math.min(500, text.length())));
mEditChip.setVisibility(View.GONE);
}
- private void showEditableText(CharSequence text) {
- showTextPreview(text);
+ private void showEditableText(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenTextPreview : mTextPreview;
+ showTextPreview(text, textView);
mEditChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mEditChip.setAlpha(1f);
@@ -422,32 +473,36 @@
mContext.getString(R.string.clipboard_edit_text_description));
View.OnClickListener listener = v -> editText();
mEditChip.setOnClickListener(listener);
- mTextPreview.setOnClickListener(listener);
+ textView.setOnClickListener(listener);
}
- private void showEditableImage(Uri uri) {
- ContentResolver resolver = mContext.getContentResolver();
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- mImagePreview.setImageBitmap(thumbnail);
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
- return;
- }
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.VISIBLE);
+ private void showEditableImage(Uri uri, boolean isSensitive) {
mEditChip.setAlpha(1f);
mActionContainerBackground.setVisibility(View.VISIBLE);
View.OnClickListener listener = v -> editImage(uri);
+ if (isSensitive) {
+ showSinglePreview(mHiddenImagePreview);
+ mHiddenImagePreview.setOnClickListener(listener);
+ } else {
+ showSinglePreview(mImagePreview);
+ ContentResolver resolver = mContext.getContentResolver();
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ mImagePreview.setImageBitmap(thumbnail);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ }
+ mImagePreview.setOnClickListener(listener);
+ }
mEditChip.setOnClickListener(listener);
mEditChip.setContentDescription(
mContext.getString(R.string.clipboard_edit_image_description));
- mImagePreview.setOnClickListener(listener);
}
private Intent getRemoteCopyIntent(ClipData clipData) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 0d89879..b54b832 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -20,10 +20,12 @@
import android.app.Activity;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -41,6 +43,7 @@
private EditText mEditText;
private ClipboardManager mClipboardManager;
private TextView mAttribution;
+ private boolean mSensitive;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -72,6 +75,9 @@
}
mEditText.setText(clip.getItemAt(0).getText());
mEditText.requestFocus();
+ mSensitive = clip.getDescription().getExtras() != null
+ && clip.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
mClipboardManager.addPrimaryClipChangedListener(this);
}
@@ -88,6 +94,9 @@
private void saveToClipboard() {
ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive);
+ clip.getDescription().setExtras(extras);
mClipboardManager.setPrimaryClip(clip);
hideImeAndFinish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 5d154c3..4e48a52 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -38,7 +38,6 @@
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -80,8 +79,19 @@
import dagger.Provides;
/**
- * A dagger module for injecting default implementations of components of System UI that may be
- * overridden by the System UI implementation.
+ * A dagger module for injecting default implementations of components of System UI.
+ *
+ * Variants of SystemUI should make a copy of this, include it in their component, and customize it
+ * as needed.
+ *
+ * This module might alternatively be named `AospSystemUIModule`, `PhoneSystemUIModule`,
+ * or `BasicSystemUIModule`.
+ *
+ * Nothing in the module should be strictly required. Each piece should either be swappable with
+ * a different implementation or entirely removable.
+ *
+ * This is different from {@link SystemUIModule} which should be used for pieces of required
+ * SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
MediaModule.class,
@@ -90,7 +100,7 @@
StartCentralSurfacesModule.class,
VolumeModule.class
})
-public abstract class SystemUIDefaultModule {
+public abstract class ReferenceSystemUIModule {
@SysUISingleton
@Provides
@@ -101,9 +111,6 @@
}
@Binds
- abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
-
- @Binds
abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
@@ -148,6 +155,7 @@
return spC;
}
+ /** */
@Binds
@SysUISingleton
public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 701972a..5d34a69 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -73,7 +73,7 @@
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
- SystemUIDefaultModule.class})
+ ReferenceSystemUIModule.class})
public interface SysUIComponent {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index bbeb66c..535eff8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -100,8 +100,14 @@
import dagger.Provides;
/**
- * A dagger module for injecting components of System UI that are not overridden by the System UI
- * implementation.
+ * A dagger module for injecting components of System UI that are required by System UI.
+ *
+ * If your feature can be excluded, subclassed, or re-implemented by a variant of SystemUI, put
+ * your Dagger Module in {@link ReferenceSystemUIModule} and/or any variant modules that
+ * rely on the feature.
+ *
+ * Adding an entry in this file means that _all_ variants of SystemUI will receive that code. They
+ * may not appreciate that.
*/
@Module(includes = {
AppOpsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
index d775ad3..3c0748e 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -16,11 +16,13 @@
package com.android.systemui.decor
import android.annotation.IdRes
+import android.annotation.NonNull
import android.content.Context
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import com.android.systemui.RegionInterceptingFrameLayout
+import java.io.PrintWriter
class OverlayWindow(private val context: Context) {
@@ -100,4 +102,13 @@
}
}
}
+
+ fun dump(@NonNull pw: PrintWriter, name: String) {
+ pw.println(" $name=")
+ pw.println(" rootView=$rootView")
+ for (i in 0 until rootView.childCount) {
+ val child = rootView.getChildAt(i)
+ pw.println(" child[$i]=$child")
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 74044e2..1cc5df5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -39,7 +39,6 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
@@ -94,7 +93,6 @@
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
private final KeyguardStateController mKeyguardStateController;
- private final BatteryController mBatteryController;
private final UiEventLogger mUiEventLogger;
private final DevicePostureController mDevicePostureController;
@@ -186,8 +184,7 @@
@Main DelayableExecutor mainExecutor,
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
- DevicePostureController devicePostureController,
- BatteryController batteryController) {
+ DevicePostureController devicePostureController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -208,7 +205,6 @@
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
- mBatteryController = batteryController;
}
private final DevicePostureController.Callback mDevicePostureCallback =
posture -> {
@@ -320,12 +316,7 @@
gentleWakeUp(pulseReason);
} else if (isPickup) {
if (shouldDropPickupEvent()) {
- mDozeLog.traceSensorEventDropped(
- pulseReason,
- "keyguardOccluded="
- + mKeyguardStateController.isOccluded()
- + " pluggedInWireless="
- + mBatteryController.isPluggedInWireless());
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
return;
}
gentleWakeUp(pulseReason);
@@ -356,7 +347,7 @@
}
private boolean shouldDropPickupEvent() {
- return mKeyguardStateController.isOccluded() || mBatteryController.isPluggedInWireless();
+ return mKeyguardStateController.isOccluded();
}
private void gentleWakeUp(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index e963c39..afa7d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -151,7 +151,7 @@
/***************************************/
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
- public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
+ public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 14a7e3c..404e531 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -857,6 +857,13 @@
}
/**
+ * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
+ */
+ fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
+ return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
+ }
+
+ /**
* Whether we are playing a canned unlock animation, vs. unlocking from a touch gesture such as
* a swipe.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4d59f1a..e379d76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2625,6 +2625,9 @@
// The remote animation is over, so we're not going away anymore.
mKeyguardStateController.notifyKeyguardGoingAway(false);
+
+ // Dispatch the callback on animation finishes.
+ mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
});
mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index e16da89..25effec 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -89,7 +89,7 @@
init {
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
- thread(start = true, priority = Thread.NORM_PRIORITY) {
+ thread(start = true, name = "LogBuffer-$name", priority = Thread.NORM_PRIORITY) {
try {
while (true) {
echoToDesiredEndpoints(echoMessageQueue.take())
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 3483bc3..8002fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -845,7 +845,8 @@
uid,
interactedSubcardRank,
interactedSubcardCardinality,
- receivedLatencyMillis
+ receivedLatencyMillis,
+ null // Media cards cannot have subcards.
)
/* ktlint-disable max-line-length */
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 2fec499..b960142 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -97,7 +97,7 @@
* A view controller used for Media Playback.
*/
public class MediaControlPanel {
- private static final String TAG = "MediaControlPanel";
+ protected static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
@@ -106,7 +106,7 @@
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
- private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
+ protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
// Event types logged by smartspace
private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
@@ -149,6 +149,7 @@
private RecommendationViewHolder mRecommendationViewHolder;
private String mKey;
private MediaData mMediaData;
+ private SmartspaceMediaData mRecommendationData;
private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
@@ -448,6 +449,7 @@
bindOutputSwitcherChip(data);
bindGutsMenuForPlayer(data);
+ bindPlayerContentDescription(data);
bindScrubbingTime(data);
bindActionButtons(data);
@@ -541,12 +543,6 @@
}
private boolean bindSongMetadata(MediaData data) {
- // Accessibility label
- mMediaViewHolder.getPlayer().setContentDescription(
- mContext.getString(
- R.string.controls_media_playing_item_description,
- data.getSong(), data.getArtist(), data.getApp()));
-
TextView titleText = mMediaViewHolder.getTitleText();
TextView artistText = mMediaViewHolder.getArtistText();
return mMetadataAnimationHandler.setNext(
@@ -568,6 +564,48 @@
});
}
+ // We may want to look into unifying this with bindRecommendationContentDescription if/when we
+ // do a refactor of this class.
+ private void bindPlayerContentDescription(MediaData data) {
+ if (mMediaViewHolder == null) {
+ return;
+ }
+
+ CharSequence contentDescription;
+ if (mMediaViewController.isGutsVisible()) {
+ contentDescription = mMediaViewHolder.getGutsViewHolder().getGutsText().getText();
+ } else if (data != null) {
+ contentDescription = mContext.getString(
+ R.string.controls_media_playing_item_description,
+ data.getSong(),
+ data.getArtist(),
+ data.getApp());
+ } else {
+ contentDescription = null;
+ }
+ mMediaViewHolder.getPlayer().setContentDescription(contentDescription);
+ }
+
+ private void bindRecommendationContentDescription(SmartspaceMediaData data) {
+ if (mRecommendationViewHolder == null) {
+ return;
+ }
+
+ CharSequence contentDescription;
+ if (mMediaViewController.isGutsVisible()) {
+ contentDescription =
+ mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
+ } else if (data != null) {
+ contentDescription = mContext.getString(
+ R.string.controls_media_smartspace_rec_description,
+ data.getAppName(mContext));
+ } else {
+ contentDescription = null;
+ }
+
+ mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription);
+ }
+
private void bindArtworkAndColors(MediaData data, boolean updateBackground) {
final int reqId = mArtworkNextBindRequestId++;
if (updateBackground) {
@@ -950,6 +988,7 @@
return;
}
+ mRecommendationData = data;
mSmartspaceId = SmallHash.hash(data.getTargetId());
mPackageName = data.getPackageName();
mInstanceId = data.getInstanceId();
@@ -965,6 +1004,12 @@
return;
}
+ CharSequence appName = data.getAppName(mContext);
+ if (appName == null) {
+ Log.w(TAG, "Fail to get media recommendation's app name");
+ return;
+ }
+
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
@@ -972,28 +1017,11 @@
headerLogoImageView.setImageDrawable(icon);
fetchAndUpdateRecommendationColors(icon);
- // Set up media source app's label text.
- CharSequence appName = getAppName(data.getCardAction());
- if (TextUtils.isEmpty(appName)) {
- Intent launchIntent =
- packageManager.getLaunchIntentForPackage(data.getPackageName());
- if (launchIntent != null) {
- ActivityInfo launchActivity = launchIntent.resolveActivityInfo(packageManager, 0);
- appName = launchActivity.loadLabel(packageManager);
- } else {
- Log.w(TAG, "Package " + data.getPackageName()
- + " does not have a main launcher activity. Fallback to full app name");
- appName = packageManager.getApplicationLabel(applicationInfo);
- }
- }
-
// Set up media rec card's tap action if applicable.
TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
/* interactedSubcardRank */ -1);
- // Set up media rec card's accessibility label.
- recommendationCard.setContentDescription(
- mContext.getString(R.string.controls_media_smartspace_rec_description, appName));
+ bindRecommendationContentDescription(data);
List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
@@ -1175,6 +1203,11 @@
mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.closeGuts(immediate);
+ if (mMediaViewHolder != null) {
+ bindPlayerContentDescription(mMediaData);
+ } else if (mRecommendationViewHolder != null) {
+ bindRecommendationContentDescription(mRecommendationData);
+ }
}
private void closeGuts() {
@@ -1188,6 +1221,11 @@
mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.openGuts();
+ if (mMediaViewHolder != null) {
+ bindPlayerContentDescription(mMediaData);
+ } else if (mRecommendationViewHolder != null) {
+ bindRecommendationContentDescription(mRecommendationData);
+ }
mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
}
@@ -1302,17 +1340,6 @@
});
}
- /** Returns the upstream app name if available. */
- @Nullable
- private String getAppName(SmartspaceAction action) {
- if (action == null || action.getIntent() == null
- || action.getIntent().getExtras() == null) {
- return null;
- }
-
- return action.getIntent().getExtras().getString(KEY_SMARTSPACE_APP_NAME);
- }
-
/** Returns if the Smartspace action will open the activity in foreground. */
private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
if (action == null || action.getIntent() == null
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index f85078c..11ee657 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.statusbar.policy.ConfigurationController
import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -44,6 +45,7 @@
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
+ private val configurationController: ConfigurationController,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager
@@ -79,7 +81,7 @@
oldEntry?.stop()
}
var entry = entries[key]
- if (entry == null || entry?.token != data.token) {
+ if (entry == null || entry.token != data.token) {
entry?.stop()
if (data.device != null) {
// If we were already provided device info (e.g. from RCN), keep that and don't
@@ -118,10 +120,9 @@
override fun dump(pw: PrintWriter, args: Array<String>) {
with(pw) {
println("MediaDeviceManager state:")
- entries.forEach {
- key, entry ->
+ entries.forEach { (key, entry) ->
println(" key=$key")
- entry.dump(pw, args)
+ entry.dump(pw)
}
}
}
@@ -165,6 +166,12 @@
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
+ private val configListener = object : ConfigurationController.ConfigurationListener {
+ override fun onLocaleListChanged() {
+ updateCurrent()
+ }
+ }
+
@AnyThread
fun start() = bgExecutor.execute {
localMediaManager.registerCallback(this)
@@ -174,6 +181,7 @@
controller?.registerCallback(this)
updateCurrent()
started = true
+ configurationController.addCallback(configListener)
}
@AnyThread
@@ -183,9 +191,10 @@
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
muteAwaitConnectionManager?.stopListening()
+ configurationController.removeCallback(configListener)
}
- fun dump(pw: PrintWriter, args: Array<String>) {
+ fun dump(pw: PrintWriter) {
val routingSession = controller?.let {
mr2manager.getRoutingSessionForMediaController(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 1437c96..7eccb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -278,13 +278,14 @@
/**
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
*/
- private fun squishViewState(
+ @VisibleForTesting
+ internal fun squishViewState(
viewState: TransitionViewState,
squishFraction: Float
): TransitionViewState {
val squishedViewState = viewState.copy()
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
- val albumArtViewState = viewState.widgetStates.get(R.id.album_art)
+ val albumArtViewState = squishedViewState.widgetStates.get(R.id.album_art)
if (albumArtViewState != null) {
albumArtViewState.height = squishedViewState.height
}
@@ -317,6 +318,7 @@
if (transitionLayout == null) {
return null
}
+
// Not cached. Let's create a new measurement
if (state.expansion == 0.0f || state.expansion == 1.0f) {
result = transitionLayout!!.calculateViewState(
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 4f598ff..40a5653 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -52,8 +52,10 @@
private final MediaBrowserFactory mBrowserFactory;
private final ResumeMediaBrowserLogger mLogger;
private final ComponentName mComponentName;
+ private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback();
private MediaBrowser mMediaBrowser;
+ @Nullable private MediaController mMediaController;
/**
* Initialize a new media browser
@@ -90,6 +92,7 @@
mComponentName,
mConnectionCallback,
rootHints);
+ updateMediaController();
mLogger.logConnection(mComponentName, "findRecentMedia");
mMediaBrowser.connect();
}
@@ -154,7 +157,8 @@
@Override
public void onConnected() {
Log.d(TAG, "Service connected for " + mComponentName);
- if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+ updateMediaController();
+ if (isBrowserConnected()) {
String root = mMediaBrowser.getRoot();
if (!TextUtils.isEmpty(root)) {
if (mCallback != null) {
@@ -207,6 +211,7 @@
mMediaBrowser.disconnect();
}
mMediaBrowser = null;
+ updateMediaController();
}
/**
@@ -225,7 +230,8 @@
@Override
public void onConnected() {
Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
- if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
+ updateMediaController();
+ if (!isBrowserConnected()) {
if (mCallback != null) {
mCallback.onError();
}
@@ -259,6 +265,7 @@
disconnect();
}
}, rootHints);
+ updateMediaController();
mLogger.logConnection(mComponentName, "restart");
mMediaBrowser.connect();
}
@@ -273,7 +280,7 @@
* @return the token, or null if the MediaBrowser is null or disconnected
*/
public MediaSession.Token getToken() {
- if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
+ if (!isBrowserConnected()) {
return null;
}
return mMediaBrowser.getSessionToken();
@@ -305,10 +312,39 @@
mComponentName,
mConnectionCallback,
rootHints);
+ updateMediaController();
mLogger.logConnection(mComponentName, "testConnection");
mMediaBrowser.connect();
}
+ /** Updates mMediaController based on our current browser values. */
+ private void updateMediaController() {
+ MediaSession.Token controllerToken =
+ mMediaController != null ? mMediaController.getSessionToken() : null;
+ MediaSession.Token currentToken = getToken();
+ boolean areEqual = (controllerToken == null && currentToken == null)
+ || (controllerToken != null && controllerToken.equals(currentToken));
+ if (areEqual) {
+ return;
+ }
+
+ // Whenever the token changes, un-register the callback on the old controller (if we have
+ // one) and create a new controller with the callback attached.
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mMediaControllerCallback);
+ }
+ if (currentToken != null) {
+ mMediaController = createMediaController(currentToken);
+ mMediaController.registerCallback(mMediaControllerCallback);
+ } else {
+ mMediaController = null;
+ }
+ }
+
+ private boolean isBrowserConnected() {
+ return mMediaBrowser != null && mMediaBrowser.isConnected();
+ }
+
/**
* Interface to handle results from ResumeMediaBrowser
*/
@@ -335,4 +371,12 @@
ResumeMediaBrowser browser) {
}
}
+
+ private class SessionDestroyCallback extends MediaController.Callback {
+ @Override
+ public void onSessionDestroyed() {
+ mLogger.logSessionDestroyed(isBrowserConnected(), mComponentName);
+ disconnect();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
index ccc5edc..41f7354 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
@@ -48,6 +48,27 @@
},
{ "Disconnecting browser for component $str1" }
)
+
+ /**
+ * Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
+ * event.
+ *
+ * @param isBrowserConnected true if there's a currently connected
+ * [android.media.browse.MediaBrowser] and false otherwise.
+ * @param componentName the component name for the [ResumeMediaBrowser] that triggered this log.
+ */
+ fun logSessionDestroyed(
+ isBrowserConnected: Boolean,
+ componentName: ComponentName
+ ) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = isBrowserConnected
+ str1 = componentName.toShortString()
+ },
+ { "Session destroyed. Active browser = $bool1. Browser component = $str1." }
+ )
}
private const val TAG = "MediaBrowser"
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
index 50a96f6..c8f17d9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -17,8 +17,13 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.text.TextUtils
+import android.util.Log
import com.android.internal.logging.InstanceId
+import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
@@ -67,6 +72,32 @@
* Returns the list of [recommendations] that have valid data.
*/
fun getValidRecommendations() = recommendations.filter { it.icon != null }
+
+ /** Returns the upstream app name if available. */
+ fun getAppName(context: Context): CharSequence? {
+ val nameFromAction = cardAction?.intent?.extras?.getString(KEY_SMARTSPACE_APP_NAME)
+ if (!TextUtils.isEmpty(nameFromAction)) {
+ return nameFromAction
+ }
+
+ val packageManager = context.packageManager
+ packageManager.getLaunchIntentForPackage(packageName)?.let {
+ val launchActivity = it.resolveActivityInfo(packageManager, 0)
+ return launchActivity.loadLabel(packageManager)
+ }
+
+ Log.w(
+ TAG,
+ "Package $packageName does not have a main launcher activity. " +
+ "Fallback to full app name")
+ return try {
+ val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
+ packageManager.getApplicationLabel(applicationInfo)
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
}
const val NUM_REQUIRED_RECOMMENDATIONS = 3
+private val TAG = SmartspaceMediaData::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index a397f32..ec472c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -143,6 +143,7 @@
if (mController.isTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
+ setUpDeviceIcon(device);
mProgressBar.getIndeterminateDrawable().setColorFilter(
new PorterDuffColorFilter(
mController.getColorItemContent(),
@@ -151,11 +152,13 @@
false /* showSeekBar*/,
true /* showProgressBar */, false /* showStatus */);
} else {
+ setUpDeviceIcon(device);
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
}
} else {
// Set different layout for each device
if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
+ setUpDeviceIcon(device);
mTitleText.setAlpha(DEVICE_CONNECTED_ALPHA);
mTitleIcon.setAlpha(DEVICE_CONNECTED_ALPHA);
mStatusIcon.setImageDrawable(
@@ -167,6 +170,7 @@
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
} else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
+ setUpDeviceIcon(device);
mProgressBar.getIndeterminateDrawable().setColorFilter(
new PorterDuffColorFilter(
mController.getColorItemContent(),
@@ -176,7 +180,12 @@
true /* showProgressBar */, false /* showStatus */);
} else if (mController.getSelectedMediaDevice().size() > 1
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+ boolean isDeviceDeselectable = isDeviceIncluded(
+ mController.getDeselectableMediaDevice(), device);
mTitleText.setTextColor(mController.getColorItemContent());
+ mTitleIcon.setImageDrawable(
+ mContext.getDrawable(R.drawable.media_output_icon_volume));
+ mTitleIcon.setColorFilter(mController.getColorItemContent());
setSingleLineLayout(getItemTitle(device), true /* bFocused */,
true /* showSeekBar */,
false /* showProgressBar */, false /* showStatus */);
@@ -184,28 +193,36 @@
mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(true);
- mCheckBox.setOnCheckedChangeListener(
- (buttonView, isChecked) -> onGroupActionTriggered(false, device));
+ mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable
+ ? (buttonView, isChecked) -> onGroupActionTriggered(false, device)
+ : null);
+ mCheckBox.setEnabled(isDeviceDeselectable);
+ mCheckBox.setAlpha(
+ isDeviceDeselectable ? DEVICE_CONNECTED_ALPHA
+ : DEVICE_DISCONNECTED_ALPHA
+ );
setCheckBoxColor(mCheckBox, mController.getColorItemContent());
initSeekbar(device, isCurrentSeekbarInvisible);
mEndTouchArea.setVisibility(View.VISIBLE);
mEndTouchArea.setOnClickListener(null);
- mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick());
+ mEndTouchArea.setOnClickListener(
+ isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
mEndTouchArea.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_YES);
setUpContentDescriptionForView(mEndTouchArea, true, device);
} else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
- mStatusIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.media_output_status_check));
- mStatusIcon.setColorFilter(mController.getColorItemContent());
+ mTitleIcon.setImageDrawable(
+ mContext.getDrawable(R.drawable.media_output_icon_volume));
+ mTitleIcon.setColorFilter(mController.getColorItemContent());
mTitleText.setTextColor(mController.getColorItemContent());
setSingleLineLayout(getItemTitle(device), true /* bFocused */,
true /* showSeekBar */,
- false /* showProgressBar */, true /* showStatus */);
+ false /* showProgressBar */, false /* showStatus */);
initSeekbar(device, isCurrentSeekbarInvisible);
setUpContentDescriptionForView(mContainerLayout, false, device);
mCurrentActivePosition = position;
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ setUpDeviceIcon(device);
mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(false);
@@ -218,6 +235,7 @@
false /* showSeekBar */,
false /* showProgressBar */, false /* showStatus */);
} else {
+ setUpDeviceIcon(device);
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 5c2cc0b..b407e76f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -166,15 +166,6 @@
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
mDeviceId = device.getId();
- ThreadUtils.postOnBackgroundThread(() -> {
- Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
- ThreadUtils.postOnMainThread(() -> {
- if (!TextUtils.equals(mDeviceId, device.getId())) {
- return;
- }
- mTitleIcon.setImageIcon(icon);
- });
- });
}
abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
@@ -414,5 +405,17 @@
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
}
+
+ protected void setUpDeviceIcon(MediaDevice device) {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
+ ThreadUtils.postOnMainThread(() -> {
+ if (!TextUtils.equals(mDeviceId, device.getId())) {
+ return;
+ }
+ mTitleIcon.setImageIcon(icon);
+ });
+ });
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 8dcca3d..1a727f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -76,6 +76,7 @@
private static final String PREF_NAME = "MediaOutputDialog";
private static final String PREF_IS_LE_BROADCAST_FIRST_LAUNCH = "PrefIsLeBroadcastFirstLaunch";
private static final boolean DEBUG = true;
+ private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final RecyclerView.LayoutManager mLayoutManager;
@@ -119,7 +120,7 @@
Log.d(TAG, "onBroadcastStarted(), reason = " + reason
+ ", broadcastId = " + broadcastId);
}
- mMainThreadHandler.post(() -> startLeBroadcastDialog());
+ mMainThreadHandler.post(() -> handleLeBroadcastStarted());
}
@Override
@@ -127,7 +128,8 @@
if (DEBUG) {
Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
}
- handleLeBroadcastStartFailed();
+ mMainThreadHandler.postDelayed(() -> handleLeBroadcastStartFailed(),
+ HANDLE_BROADCAST_FAILED_DELAY);
}
@Override
@@ -137,7 +139,7 @@
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId
+ ", metadata = " + metadata);
}
- mMainThreadHandler.post(() -> refresh());
+ mMainThreadHandler.post(() -> handleLeBroadcastMetadataChanged());
}
@Override
@@ -146,7 +148,7 @@
Log.d(TAG, "onBroadcastStopped(), reason = " + reason
+ ", broadcastId = " + broadcastId);
}
- mMainThreadHandler.post(() -> refresh());
+ mMainThreadHandler.post(() -> handleLeBroadcastStopped());
}
@Override
@@ -154,7 +156,7 @@
if (DEBUG) {
Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
}
- mMainThreadHandler.post(() -> refresh());
+ mMainThreadHandler.post(() -> handleLeBroadcastStopFailed());
}
@Override
@@ -163,7 +165,7 @@
Log.d(TAG, "onBroadcastUpdated(), reason = " + reason
+ ", broadcastId = " + broadcastId);
}
- mMainThreadHandler.post(() -> refresh());
+ mMainThreadHandler.post(() -> handleLeBroadcastUpdated());
}
@Override
@@ -172,7 +174,7 @@
Log.d(TAG, "onBroadcastUpdateFailed(), reason = " + reason
+ ", broadcastId = " + broadcastId);
}
- mMainThreadHandler.post(() -> refresh());
+ mMainThreadHandler.post(() -> handleLeBroadcastUpdateFailed());
}
@Override
@@ -384,10 +386,34 @@
Bitmap.createScaledBitmap(bitmap, size, size, false));
}
- protected void handleLeBroadcastStartFailed() {
+ public void handleLeBroadcastStarted() {
+ startLeBroadcastDialog();
+ }
+
+ public void handleLeBroadcastStartFailed() {
mStopButton.setText(R.string.media_output_broadcast_start_failed);
mStopButton.setEnabled(false);
- mMainThreadHandler.postDelayed(() -> refresh(), 3000);
+ refresh();
+ }
+
+ public void handleLeBroadcastMetadataChanged() {
+ refresh();
+ }
+
+ public void handleLeBroadcastStopped() {
+ refresh();
+ }
+
+ public void handleLeBroadcastStopFailed() {
+ refresh();
+ }
+
+ public void handleLeBroadcastUpdated() {
+ refresh();
+ }
+
+ public void handleLeBroadcastUpdateFailed() {
+ refresh();
}
protected void startLeBroadcast() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index dd4f1d6..8f06546 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -61,6 +61,10 @@
private ImageView mBroadcastCodeEdit;
private AlertDialog mAlertDialog;
private TextView mBroadcastErrorMessage;
+ private int mRetryCount = 0;
+ private String mCurrentBroadcastName;
+ private String mCurrentBroadcastCode;
+ private boolean mIsStopbyUpdateBroadcastCode = false;
static final int METADATA_BROADCAST_NAME = 0;
static final int METADATA_BROADCAST_CODE = 1;
@@ -144,8 +148,6 @@
//init UI component
mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view);
- //Set the QR code view
- setQrCodeView();
mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info);
mBroadcastNotify.setOnClickListener(v -> {
@@ -171,8 +173,16 @@
launchBroadcastUpdatedDialog(true, mBroadcastCode.getText().toString());
});
- mBroadcastName.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_NAME));
- mBroadcastCode.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_CODE));
+ refreshUi();
+ }
+
+ private void refreshUi() {
+ setQrCodeView();
+
+ mCurrentBroadcastName = getBroadcastMetadataInfo(METADATA_BROADCAST_NAME);
+ mCurrentBroadcastCode = getBroadcastMetadataInfo(METADATA_BROADCAST_CODE);
+ mBroadcastName.setText(mCurrentBroadcastName);
+ mBroadcastCode.setText(mCurrentBroadcastCode);
}
private void inflateBroadcastInfoArea() {
@@ -239,52 +249,99 @@
}
if (isBroadcastCode) {
- handleBroadcastCodeUpdated(updatedString);
+ /* If the user wants to update the Broadcast Code, the Broadcast session should be
+ * stopped then used the new Broadcast code to start the Broadcast.
+ */
+ mIsStopbyUpdateBroadcastCode = true;
+ mMediaOutputController.setBroadcastCode(updatedString);
+ if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
+ handleLeBroadcastStopFailed();
+ return;
+ }
} else {
- handleBroadcastNameUpdated(updatedString);
+ /* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast
+ * session. Only use the new Broadcast name to update the broadcast session.
+ */
+ mMediaOutputController.setBroadcastName(updatedString);
+ if (!mMediaOutputController.updateBluetoothLeBroadcast()) {
+ handleLeBroadcastUpdateFailed();
+ }
}
}
- private void handleBroadcastNameUpdated(String name) {
- // TODO(b/230473995) Add the retry mechanism and error handling when update fails
- String currentName = mMediaOutputController.getBroadcastName();
- int retryCount = MAX_BROADCAST_INFO_UPDATE;
- mMediaOutputController.setBroadcastName(name);
- if (!mMediaOutputController.updateBluetoothLeBroadcast()) {
- mMediaOutputController.setBroadcastName(currentName);
- handleLeUpdateBroadcastFailed(retryCount);
+ @Override
+ public void handleLeBroadcastStarted() {
+ mRetryCount = 0;
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+ refreshUi();
+ }
+
+ @Override
+ public void handleLeBroadcastStartFailed() {
+ mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ mRetryCount++;
+
+ handleUpdateFailedUi();
+ }
+
+ @Override
+ public void handleLeBroadcastMetadataChanged() {
+ refreshUi();
+ }
+
+ @Override
+ public void handleLeBroadcastUpdated() {
+ mRetryCount = 0;
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+ refreshUi();
+ }
+
+ @Override
+ public void handleLeBroadcastUpdateFailed() {
+ //Change the value in shared preferences back to it original value
+ mMediaOutputController.setBroadcastName(mCurrentBroadcastName);
+ mRetryCount++;
+
+ handleUpdateFailedUi();
+ }
+
+ @Override
+ public void handleLeBroadcastStopped() {
+ if (mIsStopbyUpdateBroadcastCode) {
+ mIsStopbyUpdateBroadcastCode = false;
+ mRetryCount = 0;
+ if (!mMediaOutputController.startBluetoothLeBroadcast()) {
+ handleLeBroadcastStartFailed();
+ return;
+ }
+ } else {
+ dismiss();
}
}
- private void handleBroadcastCodeUpdated(String newPassword) {
- // TODO(b/230473995) Add the retry mechanism and error handling when update fails
- String currentPassword = mMediaOutputController.getBroadcastCode();
- int retryCount = MAX_BROADCAST_INFO_UPDATE;
- if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
- mMediaOutputController.setBroadcastCode(currentPassword);
- handleLeUpdateBroadcastFailed(retryCount);
- return;
- }
+ @Override
+ public void handleLeBroadcastStopFailed() {
+ //Change the value in shared preferences back to it original value
+ mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ mRetryCount++;
- mMediaOutputController.setBroadcastCode(newPassword);
- if (!mMediaOutputController.startBluetoothLeBroadcast()) {
- mMediaOutputController.setBroadcastCode(currentPassword);
- handleLeUpdateBroadcastFailed(retryCount);
- return;
- }
-
- mAlertDialog.dismiss();
+ handleUpdateFailedUi();
}
- private void handleLeUpdateBroadcastFailed(int retryCount) {
+ private void handleUpdateFailedUi() {
final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
mBroadcastErrorMessage.setVisibility(View.VISIBLE);
- if (retryCount < MAX_BROADCAST_INFO_UPDATE) {
+ if (mRetryCount < MAX_BROADCAST_INFO_UPDATE) {
if (positiveBtn != null) {
positiveBtn.setEnabled(true);
}
mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error);
} else {
+ mRetryCount = 0;
mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
index 1145891..6379960 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
@@ -1,11 +1,43 @@
# Media Tap-To-Transfer
+## Overview
This package (and child packages) include code for the media tap-to-transfer feature, which
allows users to easily transfer playing media between devices.
-In media transfer, there are two devices: the *sender* and the *receiver*. The sender device will
-start and stop media casts to the receiver device. On both devices, System UI will display a chip
-informing the user about the media cast occurring.
+In media transfer, there are two devices: the **sender** and the **receiver**. The sender device
+will start and stop media casts to the receiver device. On both devices, System UI will display a
+chip informing the user about the media cast occurring.
-This package is structured so that the sender code is in the sender package, the receiver code is
-in the receiver package, and code that's shared between them is in the common package.
+**Important**: System UI is **not responsible** for performing the media transfer. System UI
+**only** displays an informational chip; external clients are responsible for performing the media
+transfer and informing System UI about the transfer status.
+
+## Information flow
+External clients notify System UI about the transfer status by calling `@SystemApi`s in
+`StatusBarManager`. For the sender device, use the `updateMediaTapToTransferSenderDisplay` API; for
+the receiver, use the `updateMediaTapToTransferReceiverDisplay` API. The APIs eventually flow into
+SystemUI's `CommandQueue`, which then notifies callbacks about the new state.
+`MediaTttChipControllerSender` implements the sender callback, and `MediaTttChipControllerReceiver`
+implements the receiver callback. These controllers will then show or hide the tap-to-transfer chip
+(depending on what information was sent in the API).
+
+## Architecture
+This package is structured so that the sender code is in the `sender` package, the receiver code is
+in the `receiver` package, and code that's shared between them is in the `common` package.
+
+* The `ChipStateSender` and `ChipStateReceiver` classes are enums that describe all the possible
+ transfer states (transfer started, transfer succeeded, etc.) and include relevant parameters for
+ each state.
+* The `ChipSenderInfo` and `ChipReceiverInfo` classes are simple data classes that contain all the
+ information needed to display a chip. They include the transfer state, information about the media
+ being transferred, etc.
+* The `MediaTttChipControllerSender` and `MediaTttChipControllerReceiver` classes are responsible
+ for showing or hiding the chip and updating the chip view based on information from the
+ `ChipInfo`. `MediaTttChipControllerCommon` has all the common logic for adding and removing the
+ view to the window and also includes any display logic that can be shared between the sender and
+ receiver. The sender and receiver controller subclasses have the display logic that's specific to
+ just the sender or just the receiver.
+
+## Testing
+If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb
+commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 3bb4e64..aa38b78 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -56,6 +56,7 @@
import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
+import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
import android.annotation.IdRes;
import android.app.ActivityTaskManager;
@@ -68,8 +69,10 @@
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -91,6 +94,8 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -112,6 +117,7 @@
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
@@ -121,11 +127,13 @@
import com.android.systemui.navigationbar.buttons.DeadZone;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -153,7 +161,9 @@
import java.io.PrintWriter;
import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -199,12 +209,16 @@
private final Optional<Recents> mRecentsOptional;
private final DeviceConfigProxy mDeviceConfigProxy;
private final NavigationBarTransitions mNavigationBarTransitions;
+ private final EdgeBackGestureHandler mEdgeBackGestureHandler;
private final Optional<BackAnimation> mBackAnimation;
private final Handler mHandler;
private final UiEventLogger mUiEventLogger;
private final NavBarHelper mNavBarHelper;
private final NotificationShadeDepthController mNotificationShadeDepthController;
private final UserContextProvider mUserContextProvider;
+ private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
+ private final RegionSamplingHelper mRegionSamplingHelper;
+ private final int mNavColorSampleMargin;
private NavigationBarFrame mFrame;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -266,6 +280,16 @@
private boolean mShowOrientedHandleForImmersiveMode;
private final DeadZone mDeadZone;
private boolean mImeVisible;
+ private final Rect mSamplingBounds = new Rect();
+
+ /**
+ * When quickswitching between apps of different orientations, we draw a secondary home handle
+ * in the position of the first app's orientation. This rect represents the region of that
+ * home handle so we can apply the correct light/dark luma on that.
+ * @see {@link NavigationBar#mOrientationHandle}
+ */
+ @android.annotation.Nullable
+ private Rect mOrientedHandleSamplingRegion;
@com.android.internal.annotations.VisibleForTesting
public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
@@ -476,7 +500,7 @@
return;
}
mHasBlurs = hasBlurs;
- mView.setWindowHasBlurs(hasBlurs);
+ mRegionSamplingHelper.setWindowHasBlurs(hasBlurs);
}
};
@@ -505,6 +529,8 @@
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
UiEventLogger uiEventLogger,
NavBarHelper navBarHelper,
LightBarController mainLightBarController,
@@ -516,6 +542,7 @@
DeadZone deadZone,
DeviceConfigProxy deviceConfigProxy,
NavigationBarTransitions navigationBarTransitions,
+ EdgeBackGestureHandler edgeBackGestureHandler,
Optional<BackAnimation> backAnimation,
UserContextProvider userContextProvider) {
super(navigationBarView);
@@ -542,6 +569,7 @@
mDeadZone = deadZone;
mDeviceConfigProxy = deviceConfigProxy;
mNavigationBarTransitions = navigationBarTransitions;
+ mEdgeBackGestureHandler = edgeBackGestureHandler;
mBackAnimation = backAnimation;
mHandler = mainHandler;
mUiEventLogger = uiEventLogger;
@@ -555,6 +583,55 @@
mInputMethodManager = inputMethodManager;
mUserContextProvider = userContextProvider;
+ mNavColorSampleMargin = getResources()
+ .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
+
+ mOnComputeInternalInsetsListener = info -> {
+ // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
+ // gestural mode, the entire nav bar should be touchable.
+ if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+ // We're in 2/3 button mode OR back button force-shown in SUW
+ if (!mImeVisible) {
+ // IME not showing, take all touches
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ }
+ if (!mView.isImeRenderingNavButtons()) {
+ // IME showing but not drawing any buttons, take all touches
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ }
+ }
+
+ // When in gestural and the IME is showing, don't use the nearest region since it will
+ // take gesture space away from the IME
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
+ false /* inScreen */, false /* useNearestRegion */));
+ };
+
+ mRegionSamplingHelper = new RegionSamplingHelper(mView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ getBarTransitions().getLightTransitionsController().setIconsDark(
+ !isRegionDark, true /* animate */);
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ if (mOrientedHandleSamplingRegion != null) {
+ return mOrientedHandleSamplingRegion;
+ }
+
+ return calculateSamplingRect();
+ }
+
+ @Override
+ public boolean isSamplingEnabled() {
+ return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
+ }
+ }, mainExecutor, bgExecutor);
+
+ mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
}
@@ -568,7 +645,9 @@
// It should also has corresponding cleanup in onViewDetached.
mView.setBarTransitions(mNavigationBarTransitions);
mView.setTouchHandler(mTouchHandler);
- mView.setNavBarMode(mNavBarMode);
+ setNavBarMode(mNavBarMode);
+ mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
+ mNavigationBarTransitions.addListener(this::onBarTransition);
mView.updateRotationButton();
mView.setVisibility(
@@ -641,16 +720,19 @@
getBarTransitions().getLightTransitionsController().restoreState(mSavedState);
}
setNavigationIconHints(mNavigationIconHints);
- mView.setWindowVisible(isNavBarWindowVisible());
+ setWindowVisible(isNavBarWindowVisible());
mView.setBehavior(mBehavior);
- mView.setNavBarMode(mNavBarMode);
+ setNavBarMode(mNavBarMode);
mView.setUpdateActiveTouchRegionsCallback(
() -> mOverviewProxyService.onActiveNavBarRegionChanges(
- mView.getButtonLocations(
+ getButtonLocations(
true /* includeFloatingButtons */,
true /* inScreen */,
true /* useNearestRegion */)));
+ mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
+ mOnComputeInternalInsetsListener);
+
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
@@ -721,6 +803,8 @@
mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
mOrientationHandleGlobalLayoutListener);
}
+ mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+ mOnComputeInternalInsetsListener);
mHandler.removeCallbacks(mAutoDim);
mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
mHandler.removeCallbacks(mEnableLayoutTransitions);
@@ -800,7 +884,7 @@
mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true);
Rect boundsRounded = new Rect();
boundsOnScreen.roundOut(boundsRounded);
- mView.setOrientedHandleSamplingRegion(boundsRounded);
+ setOrientedHandleSamplingRegion(boundsRounded);
};
mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener(
mOrientationHandleGlobalLayoutListener);
@@ -861,7 +945,7 @@
mOrientationHandle.setVisibility(View.GONE);
}
mView.setVisibility(View.VISIBLE);
- mView.setOrientedHandleSamplingRegion(null);
+ setOrientedHandleSamplingRegion(null);
}
private void reconfigureHomeLongClick() {
@@ -899,7 +983,10 @@
pw.println(" mTransientShownFromGestureOnSystemBar="
+ mTransientShownFromGestureOnSystemBar);
dumpBarTransitions(pw, "mNavigationBarView", getBarTransitions());
+
+ pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
mView.dump(pw);
+ mRegionSamplingHelper.dump(pw);
}
// ----- CommandQueue Callbacks -----
@@ -935,7 +1022,7 @@
orientSecondaryHomeHandle();
}
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
- mView.setWindowVisible(isNavBarWindowVisible());
+ setWindowVisible(isNavBarWindowVisible());
}
}
@@ -1044,8 +1131,8 @@
}
private void handleTransientChanged() {
- mView.onTransientStateChanged(mTransientShown,
- mTransientShownFromGestureOnSystemBar);
+ mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown);
+
final int transitionMode = transitionMode(mTransientShown, mAppearance);
if (updateTransitionMode(transitionMode) && mLightBarController != null) {
mLightBarController.onNavigationBarModeChanged(transitionMode);
@@ -1436,6 +1523,11 @@
}
}
+ private void setWindowVisible(boolean visible) {
+ mRegionSamplingHelper.setWindowVisible(visible);
+ mView.setWindowVisible(visible);
+ }
+
/** Sets {@link AutoHideController} to the navigation bar. */
private void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
@@ -1603,7 +1695,15 @@
if (Intent.ACTION_SCREEN_OFF.equals(action)
|| Intent.ACTION_SCREEN_ON.equals(action)) {
notifyNavigationBarScreenOn();
- mView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action));
+ boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action);
+ mView.onScreenStateChanged(isScreenOn);
+ if (isScreenOn) {
+ if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ }
+ } else {
+ mRegionSamplingHelper.stop();
+ }
}
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
// The accessibility settings may be different for the new user
@@ -1639,6 +1739,133 @@
mNavigationIconHints = hints;
}
+ /**
+ * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+ * the region for all the buttons
+ * @param inScreenSpace Whether to return values in screen space or window space
+ * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
+ * @return
+ */
+ Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+ boolean useNearestRegion) {
+ if (useNearestRegion && !inScreenSpace) {
+ // We currently don't support getting the nearest region in anything but screen space
+ useNearestRegion = false;
+ }
+ Region region = new Region();
+ Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache();
+ updateButtonLocation(
+ region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion);
+ updateButtonLocation(
+ region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion);
+ updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace,
+ useNearestRegion);
+ updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace,
+ useNearestRegion);
+ updateButtonLocation(
+ region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace,
+ useNearestRegion);
+ if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) {
+ // Note: this button is floating so the nearest region doesn't apply
+ updateButtonLocation(
+ region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace);
+ } else {
+ updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(),
+ inScreenSpace, useNearestRegion);
+ }
+ return region;
+ }
+
+ private void updateButtonLocation(
+ Region region,
+ Map<View, Rect> touchRegionCache,
+ ButtonDispatcher button,
+ boolean inScreenSpace,
+ boolean useNearestRegion) {
+ if (button == null) {
+ return;
+ }
+ View view = button.getCurrentView();
+ if (view == null || !button.isVisible()) {
+ return;
+ }
+ // If the button is tappable from perspective of NearestTouchFrame, then we'll
+ // include the regions where the tap is valid instead of just the button layout location
+ if (useNearestRegion && touchRegionCache.containsKey(view)) {
+ region.op(touchRegionCache.get(view), Region.Op.UNION);
+ return;
+ }
+ updateButtonLocation(region, view, inScreenSpace);
+ }
+
+ private void updateButtonLocation(Region region, View view, boolean inScreenSpace) {
+ Rect bounds = new Rect();
+ if (inScreenSpace) {
+ view.getBoundsOnScreen(bounds);
+ } else {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+ bounds.set(location[0], location[1],
+ location[0] + view.getWidth(),
+ location[1] + view.getHeight());
+ }
+ region.op(bounds, Region.Op.UNION);
+ }
+
+ void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) {
+ mOrientedHandleSamplingRegion = orientedHandleSamplingRegion;
+ mRegionSamplingHelper.updateSamplingRect();
+ }
+
+ private Rect calculateSamplingRect() {
+ mSamplingBounds.setEmpty();
+ // TODO: Extend this to 2/3 button layout as well
+ View view = mView.getHomeHandle().getCurrentView();
+
+ if (view != null) {
+ int[] pos = new int[2];
+ view.getLocationOnScreen(pos);
+ Point displaySize = new Point();
+ view.getContext().getDisplay().getRealSize(displaySize);
+ final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin,
+ displaySize.y - mView.getNavBarHeight(),
+ pos[0] + view.getWidth() + mNavColorSampleMargin,
+ displaySize.y);
+ mSamplingBounds.set(samplingBounds);
+ }
+
+ return mSamplingBounds;
+ }
+
+ void setNavigationBarLumaSamplingEnabled(boolean enable) {
+ if (enable) {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
+ private void setNavBarMode(int mode) {
+ mView.setNavBarMode(mode, mNavigationModeController.getImeDrawsImeNavBar());
+ if (isGesturalMode(mode)) {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
+ void onBarTransition(int newMode) {
+ if (newMode == MODE_OPAQUE) {
+ // If the nav bar background is opaque, stop auto tinting since we know the icons are
+ // showing over a dark background
+ mRegionSamplingHelper.stop();
+ getBarTransitions().getLightTransitionsController().setIconsDark(
+ false /* dark */, true /* animate */);
+ } else {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ }
+ }
+
private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
@Override
public void onNavigationModeChanged(int mode) {
@@ -1655,10 +1882,8 @@
if (!canShowSecondaryHandle()) {
resetSecondaryHandle();
}
- if (mView != null) {
- mView.setNavBarMode(mode);
- mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
- }
+ setNavBarMode(mode);
+ mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 15182c1..d756af7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -244,9 +244,9 @@
@Override
public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
- final NavigationBarView navigationBarView = getNavigationBarView(displayId);
- if (navigationBarView != null) {
- navigationBarView.setNavigationBarLumaSamplingEnabled(enable);
+ final NavigationBar navigationBar = getNavigationBar(displayId);
+ if (navigationBar != null) {
+ navigationBar.setNavigationBarLumaSamplingEnabled(enable);
}
}
@@ -404,10 +404,14 @@
* {@code null} if no navigation bar on that display.
*/
public @Nullable NavigationBarView getNavigationBarView(int displayId) {
- NavigationBar navBar = mNavigationBars.get(displayId);
+ NavigationBar navBar = getNavigationBar(displayId);
return (navBar == null) ? null : navBar.getView();
}
+ private @Nullable NavigationBar getNavigationBar(int displayId) {
+ return mNavigationBars.get(displayId);
+ }
+
public void showPinningEnterExitToast(int displayId, boolean entering) {
final NavigationBarView navBarView = getNavigationBarView(displayId);
if (navBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 4d9175b..59bb2278e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -168,6 +168,7 @@
public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
mButtonDispatchers = buttonDispatchers;
+ clearDispatcherViews();
for (int i = 0; i < buttonDispatchers.size(); i++) {
initiallyFill(buttonDispatchers.valueAt(i));
}
@@ -454,12 +455,16 @@
}
}
- private void clearViews() {
+ private void clearDispatcherViews() {
if (mButtonDispatchers != null) {
for (int i = 0; i < mButtonDispatchers.size(); i++) {
mButtonDispatchers.valueAt(i).clear();
}
}
+ }
+
+ private void clearViews() {
+ clearDispatcherViews();
clearAllChildren(mHorizontal.findViewById(R.id.nav_buttons));
clearAllChildren(mVertical.findViewById(R.id.nav_buttons));
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 93bf136..f6bfd6c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import dagger.Module;
import dagger.Provides;
@@ -55,6 +56,14 @@
return barView.findViewById(R.id.navigation_bar_view);
}
+ /** */
+ @Provides
+ @NavigationBarScope
+ static EdgeBackGestureHandler provideEdgeBackGestureHandler(
+ EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
+ return factory.create(context);
+ }
+
/** A WindowManager specific to the display's context. */
@Provides
@NavigationBarScope
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 11a4b3b..6793f01 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -48,6 +48,7 @@
public static final int MIN_COLOR_ADAPT_TRANSITION_TIME = 400;
public static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+ private List<Listener> mListeners = new ArrayList<>();
/**
* Notified when the color of nav bar elements changes.
@@ -162,7 +163,9 @@
protected void onTransition(int oldMode, int newMode, boolean animate) {
super.onTransition(oldMode, newMode, animate);
applyLightsOut(animate, false /*force*/);
- mView.onBarTransition(newMode);
+ for (Listener listener : mListeners) {
+ listener.onTransition(newMode);
+ }
}
private void applyLightsOut(boolean animate, boolean force) {
@@ -255,4 +258,16 @@
pw.println(" bg color: " + mBarBackground.getColor());
pw.println(" bg frame: " + mBarBackground.getFrame());
}
+
+ void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ interface Listener {
+ void onTransition(int newMode);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 6e00ebc..3fc9afe 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -24,8 +24,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
@@ -34,15 +32,12 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.DrawableRes;
-import android.annotation.Nullable;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,8 +48,6 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -62,9 +55,10 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -78,7 +72,6 @@
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -93,10 +86,8 @@
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
-import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/** */
@@ -105,8 +96,6 @@
final static String TAG = "NavBarView";
final static boolean ALTERNATE_CAR_MODE_UI = false;
- private final RegionSamplingHelper mRegionSamplingHelper;
- private final int mNavColorSampleMargin;
// The current view is one of mHorizontal or mVertical depending on the current configuration
View mCurrentView = null;
@@ -123,11 +112,6 @@
private int mNavBarMode;
private boolean mImeDrawsImeNavBar;
- private final Region mTmpRegion = new Region();
- private final int[] mTmpPosition = new int[2];
- private Rect mTmpBounds = new Rect();
- private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>();
-
private KeyButtonDrawable mBackIcon;
private KeyButtonDrawable mHomeDefaultIcon;
private KeyButtonDrawable mRecentIcon;
@@ -138,7 +122,6 @@
private EdgeBackGestureHandler mEdgeBackGestureHandler;
private final DeadZone mDeadZone;
- private boolean mDeadZoneConsuming = false;
private NavigationBarTransitions mBarTransitions;
@Nullable
private AutoHideController mAutoHideController;
@@ -152,7 +135,6 @@
private boolean mUseCarModeUi = false;
private boolean mInCarMode = false;
private boolean mDockedStackExists;
- private boolean mImeVisible;
private boolean mScreenOn = true;
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
@@ -173,15 +155,6 @@
* fully locked mode we only show that unlocking is blocked.
*/
private ScreenPinningNotify mScreenPinningNotify;
- private Rect mSamplingBounds = new Rect();
- /**
- * When quickswitching between apps of different orientations, we draw a secondary home handle
- * in the position of the first app's orientation. This rect represents the region of that
- * home handle so we can apply the correct light/dark luma on that.
- * @see {@link NavigationBar#mOrientationHandle}
- */
- @Nullable
- private Rect mOrientedHandleSamplingRegion;
/**
* {@code true} if the IME can render the back button and the IME switcher button.
@@ -272,31 +245,6 @@
}
};
- private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
- // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown
- // while in gesture nav in SUW, the entire nav bar should be touchable.
- if (!mEdgeBackGestureHandler.isHandlingGestures()) {
- // We're in 2/3 button mode OR back button force-shown in SUW
- if (!mImeVisible) {
- // IME not showing, take all touches
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- return;
- }
-
- if (!isImeRenderingNavButtons()) {
- // IME showing but not drawing any buttons, take all touches
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- return;
- }
- }
-
- // When in gestural and the IME is showing, don't use the nearest region since it will take
- // gesture space away from the IME
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
- false /* inScreen */, false /* useNearestRegion */));
- };
-
private final RotationButtonUpdatesCallback mRotationButtonListener =
new RotationButtonUpdatesCallback() {
@Override
@@ -315,13 +263,6 @@
}
};
- private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
- if (visible && mAutoHideController != null) {
- mAutoHideController.touchAutoHide();
- }
- notifyActiveTouchRegions();
- };
-
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -333,7 +274,6 @@
mDarkIconColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
mIsVertical = false;
mLongClickableAccessibilityButton = false;
- mImeDrawsImeNavBar = Dependency.get(NavigationModeController.class).getImeDrawsImeNavBar();
// Set up the context group of buttons
mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
@@ -361,7 +301,7 @@
R.drawable.ic_sysbar_rotate_button_ccw_start_90,
R.drawable.ic_sysbar_rotate_button_cw_start_0,
R.drawable.ic_sysbar_rotate_button_cw_start_90,
- () -> getDisplay().getRotation());
+ () -> mCurrentRotation);
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
@@ -377,36 +317,10 @@
mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
mDeadZone = new DeadZone(this);
+ }
- mNavColorSampleMargin = getResources()
- .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
- mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
- .create(mContext);
- mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
- Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
- mRegionSamplingHelper = new RegionSamplingHelper(this,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- getLightTransitionsController().setIconsDark(!isRegionDark ,
- true /* animate */);
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- if (mOrientedHandleSamplingRegion != null) {
- return mOrientedHandleSamplingRegion;
- }
-
- updateSamplingRect();
- return mSamplingBounds;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
- }
- }, backgroundExecutor);
+ public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) {
+ mEdgeBackGestureHandler = edgeBackGestureHandler;
}
void setBarTransitions(NavigationBarTransitions navigationBarTransitions) {
@@ -450,28 +364,6 @@
return super.onTouchEvent(event);
}
- /**
- * If we're blurring the shade window.
- */
- public void setWindowHasBlurs(boolean hasBlurs) {
- mRegionSamplingHelper.setWindowHasBlurs(hasBlurs);
- }
-
- void onTransientStateChanged(boolean isTransient, boolean isGestureOnSystemBar) {
- mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient);
- }
-
- void onBarTransition(int newMode) {
- if (newMode == MODE_OPAQUE) {
- // If the nav bar background is opaque, stop auto tinting since we know the icons are
- // showing over a dark background
- mRegionSamplingHelper.stop();
- getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */);
- } else {
- mRegionSamplingHelper.start(mSamplingBounds);
- }
- }
-
public void abortCurrentGesture() {
getHomeButton().abortCurrentGesture();
}
@@ -581,6 +473,7 @@
mRotationButtonController.setRotationButton(mRotationContextButton,
mRotationButtonListener);
}
+ mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
}
public KeyButtonDrawable getBackDrawable() {
@@ -646,17 +539,9 @@
/** To be called when screen lock/unlock state changes */
public void onScreenStateChanged(boolean isScreenOn) {
mScreenOn = isScreenOn;
- if (isScreenOn) {
- if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
- mRegionSamplingHelper.start(mSamplingBounds);
- }
- } else {
- mRegionSamplingHelper.stop();
- }
}
public void setWindowVisible(boolean visible) {
- mRegionSamplingHelper.setWindowVisible(visible);
mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
}
@@ -681,8 +566,7 @@
if (!visible) {
mTransitionListener.onBackAltCleared();
}
- mImeVisible = visible;
- mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
+ mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible);
}
void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
@@ -774,7 +658,7 @@
/**
* Returns whether the IME is currently visible and drawing the nav buttons.
*/
- private boolean isImeRenderingNavButtons() {
+ boolean isImeRenderingNavButtons() {
return mImeDrawsImeNavBar
&& mImeCanRenderGesturalNavButtons
&& (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
@@ -923,18 +807,12 @@
wm.updateViewLayout(navbarView, lp);
}
- void setNavBarMode(int mode) {
+ void setNavBarMode(int mode, boolean imeDrawsImeNavBar) {
mNavBarMode = mode;
- mImeDrawsImeNavBar = Dependency.get(NavigationModeController.class).getImeDrawsImeNavBar();
+ mImeDrawsImeNavBar = imeDrawsImeNavBar;
mBarTransitions.onNavigationModeChanged(mNavBarMode);
mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
updateRotationButton();
-
- if (isGesturalMode(mNavBarMode)) {
- mRegionSamplingHelper.start(mSamplingBounds);
- } else {
- mRegionSamplingHelper.stop();
- }
}
public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
@@ -959,29 +837,6 @@
super.onDraw(canvas);
}
- private void updateSamplingRect() {
- mSamplingBounds.setEmpty();
- // TODO: Extend this to 2/3 button layout as well
- View view = getHomeHandle().getCurrentView();
-
- if (view != null) {
- int[] pos = new int[2];
- view.getLocationOnScreen(pos);
- Point displaySize = new Point();
- view.getContext().getDisplay().getRealSize(displaySize);
- final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin,
- displaySize.y - getNavBarHeight(),
- pos[0] + view.getWidth() + mNavColorSampleMargin,
- displaySize.y);
- mSamplingBounds.set(samplingBounds);
- }
- }
-
- void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) {
- mOrientedHandleSamplingRegion = orientedHandleSamplingRegion;
- mRegionSamplingHelper.updateSamplingRect();
- }
-
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -1003,75 +858,14 @@
notifyActiveTouchRegions();
}
- private void updateButtonTouchRegionCache() {
+ Map<View, Rect> getButtonTouchRegionCache() {
FrameLayout navBarLayout = mIsVertical
? mNavigationInflaterView.mVertical
: mNavigationInflaterView.mHorizontal;
- mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout
+ return ((NearestTouchFrame) navBarLayout
.findViewById(R.id.nav_buttons)).getFullTouchableChildRegions();
}
- /**
- * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
- * the region for all the buttons
- * @param inScreenSpace Whether to return values in screen space or window space
- * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
- * @return
- */
- Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
- boolean useNearestRegion) {
- // TODO: move this method to NavigationBar.
- // TODO: don't use member variables for temp storage like mTmpRegion.
- if (useNearestRegion && !inScreenSpace) {
- // We currently don't support getting the nearest region in anything but screen space
- useNearestRegion = false;
- }
- mTmpRegion.setEmpty();
- updateButtonTouchRegionCache();
- updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
- if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
- // Note: this button is floating so the nearest region doesn't apply
- updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
- } else {
- updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
- }
- return mTmpRegion;
- }
-
- private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
- boolean useNearestRegion) {
- if (button == null) {
- return;
- }
- View view = button.getCurrentView();
- if (view == null || !button.isVisible()) {
- return;
- }
- // If the button is tappable from perspective of NearestTouchFrame, then we'll
- // include the regions where the tap is valid instead of just the button layout location
- if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) {
- mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION);
- return;
- }
- updateButtonLocation(view, inScreenSpace);
- }
-
- private void updateButtonLocation(View view, boolean inScreenSpace) {
- if (inScreenSpace) {
- view.getBoundsOnScreen(mTmpBounds);
- } else {
- view.getLocationInWindow(mTmpPosition);
- mTmpBounds.set(mTmpPosition[0], mTmpPosition[1],
- mTmpPosition[0] + view.getWidth(),
- mTmpPosition[1] + view.getHeight());
- }
- mTmpRegion.op(mTmpBounds, Op.UNION);
- }
-
private void updateOrientationViews() {
mHorizontal = findViewById(R.id.horizontal);
mVertical = findViewById(R.id.vertical);
@@ -1083,15 +877,27 @@
return mCurrentRotation != rotation;
}
+ private void updateCurrentRotation() {
+ final int rotation = mConfiguration.windowConfiguration.getDisplayRotation();
+ if (mCurrentRotation == rotation) {
+ return;
+ }
+ mCurrentRotation = rotation;
+ mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
+ mDeadZone.onConfigurationChanged(mCurrentRotation);
+ if (DEBUG) {
+ Log.d(TAG, "updateCurrentRotation(): rot=" + mCurrentRotation);
+ }
+ }
+
private void updateCurrentView() {
resetViews();
mCurrentView = mIsVertical ? mVertical : mHorizontal;
mCurrentView.setVisibility(View.VISIBLE);
mNavigationInflaterView.setVertical(mIsVertical);
- mCurrentRotation = getContextDisplay().getRotation();
- mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
mNavigationInflaterView.updateButtonDispatchersCurrentView();
updateLayoutTransitionsEnabled();
+ updateCurrentRotation();
}
private void resetViews() {
@@ -1124,17 +930,11 @@
public void reorient() {
updateCurrentView();
-
((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
- mDeadZone.onConfigurationChanged(mCurrentRotation);
// force the low profile & disabled states into compliance
mBarTransitions.init();
- if (DEBUG) {
- Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
- }
-
// Resolve layout direction if not resolved since components changing layout direction such
// as changing languages will recreate this view and the direction will be resolved later
if (!isLayoutDirectionResolved()) {
@@ -1181,7 +981,7 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- private int getNavBarHeight() {
+ int getNavBarHeight() {
return mIsVertical
? getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
@@ -1205,6 +1005,7 @@
boolean uiCarModeChanged = updateCarMode();
updateIcons(mTmpLastConfiguration);
updateRecentsIcon();
+ updateCurrentRotation();
mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
|| mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
@@ -1272,7 +1073,6 @@
mRotationButtonController.registerListeners();
}
- getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
updateNavButtonIcons();
}
@@ -1288,11 +1088,9 @@
}
mEdgeBackGestureHandler.onNavBarDetached();
- getViewTreeObserver().removeOnComputeInternalInsetsListener(
- mOnComputeInternalInsetsListener);
}
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
final Rect r = new Rect();
final Point size = new Point();
getContextDisplay().getRealSize(size);
@@ -1319,7 +1117,6 @@
mIsVertical ? "true" : "false",
getLightTransitionsController().getCurrentDarkIntensity()));
- pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
pw.println(" mScreenOn: " + mScreenOn);
@@ -1336,7 +1133,6 @@
}
mBarTransitions.dump(pw);
mContextualButtonGroup.dump(pw);
- mRegionSamplingHelper.dump(pw);
mEdgeBackGestureHandler.dump(pw);
}
@@ -1397,13 +1193,6 @@
mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds);
});
- void setNavigationBarLumaSamplingEnabled(boolean enable) {
- if (enable) {
- mRegionSamplingHelper.start(mSamplingBounds);
- } else {
- mRegionSamplingHelper.stop();
- }
- }
interface UpdateActiveTouchRegionsCallback {
void update();
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 001a462..ea41fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -105,8 +105,7 @@
private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
private static final int MAX_NUM_LOGGED_GESTURES = 10;
- // Temporary log until b/202433017 is resolved
- static final boolean DEBUG_MISSING_GESTURE = true;
+ static final boolean DEBUG_MISSING_GESTURE = false;
static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private ISystemGestureExclusionListener mGestureExclusionListener =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index e5dc0ec..a74c596 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -363,6 +363,7 @@
initializeBackAnimation();
setVisibility(GONE);
+
Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 8b8941a..3709a86 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,6 +16,8 @@
package com.android.systemui.power.dagger;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
@@ -28,5 +30,9 @@
public interface PowerModule {
/** */
@Binds
+ EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
+
+ /** */
+ @Binds
PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index dbdb9d2..cd6eb99 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -58,7 +58,8 @@
internal companion object {
val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
- AppOpsManager.OP_PHONE_CALL_MICROPHONE)
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+ AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO)
val OPS_LOCATION = intArrayOf(
AppOpsManager.OP_COARSE_LOCATION,
AppOpsManager.OP_FINE_LOCATION)
@@ -315,6 +316,7 @@
AppOpsManager.OP_COARSE_LOCATION,
AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+ AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
else -> return null
}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 8000bdc..2c20feb 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -29,7 +29,6 @@
import android.util.Log;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
@@ -119,7 +118,6 @@
mSecureSettings = secureSettings;
mDeviceConfigProxy = proxy;
mUserTracker = userTracker;
-
mConfigEnableLockScreenButton = mContext.getResources().getBoolean(
android.R.bool.config_enableQrCodeScannerOnLockScreen);
}
@@ -258,16 +256,20 @@
}
}
+ private String getDefaultScannerActivity() {
+ return mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultQrCodeComponent);
+ }
+
private void updateQRCodeScannerActivityDetails() {
String qrCodeScannerActivity = mDeviceConfigProxy.getString(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, "");
// "" means either the flags is not available or is set to "", and in both the cases we
- // want to use R.string.def_qr_code_component
+ // want to use R.string.config_defaultQrCodeComponent
if (Objects.equals(qrCodeScannerActivity, "")) {
- qrCodeScannerActivity =
- mContext.getResources().getString(R.string.def_qr_code_component);
+ qrCodeScannerActivity = getDefaultScannerActivity();
}
String prevQrCodeScannerActivity = mQRCodeScannerActivity;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 311ee56..3d00dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -47,7 +47,7 @@
private int mMaxColumns = NO_MAX_COLUMNS;
protected int mResourceColumns;
private float mSquishinessFraction = 1f;
- private int mLastTileBottom;
+ protected int mLastTileBottom;
public TileLayout(Context context) {
this(context, null);
@@ -243,12 +243,11 @@
record.tileView.setLeftTopRightBottom(left, top, right, bottom);
}
record.tileView.setPosition(i);
- if (forLayout) {
- mLastTileBottom = record.tileView.getBottom();
- } else {
- float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
- mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale);
- }
+
+ // Set the bottom to the unoverriden squished bottom. This is to avoid fake bottoms that
+ // are only used for QQS -> QS expansion animations
+ float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
+ mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale);
}
}
@@ -258,7 +257,8 @@
}
protected int getRowTop(int row) {
- return (int) (row * (mCellHeight * mSquishinessFraction + mCellMarginVertical));
+ float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
+ return (int) (row * (mCellHeight * scale + mCellMarginVertical));
}
protected int getColumnStart(int column) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4728c67..6d9455e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -484,6 +484,7 @@
setWindowFocusable(false);
}
});
+ mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 6af6e36..48bb2af 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -18,6 +18,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -83,6 +84,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
@@ -149,7 +151,6 @@
private ImageView mActionsContainerBackground;
private HorizontalScrollView mActionsContainer;
private LinearLayout mActionsView;
- private ImageView mBackgroundProtection;
private FrameLayout mDismissButton;
private OverlayActionChip mShareChip;
private OverlayActionChip mEditChip;
@@ -167,6 +168,9 @@
private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+ private long mDefaultTimeoutOfTimeoutHandler;
+
private enum PendingInteraction {
PREVIEW,
EDIT,
@@ -190,6 +194,7 @@
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mResources = mContext.getResources();
+ mInteractionJankMonitor = getInteractionJankMonitorInstance();
mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
@@ -230,6 +235,14 @@
});
}
+ private InteractionJankMonitor getInteractionJankMonitorInstance() {
+ return InteractionJankMonitor.getInstance();
+ }
+
+ void setDefaultTimeoutMillis(long timeout) {
+ mDefaultTimeoutOfTimeoutHandler = timeout;
+ }
+
public void hideScrollChip() {
mScrollChip.setVisibility(View.GONE);
}
@@ -345,8 +358,6 @@
R.id.actions_container_background));
mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
- mBackgroundProtection = requireNonNull(
- findViewById(R.id.screenshot_actions_background));
mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
@@ -394,18 +405,13 @@
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
mPackageName);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- mBackgroundProtection.animate()
- .alpha(0).setDuration(animation.getDuration()).start();
- }
- });
}
@Override
public void onDismissComplete() {
+ if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) {
+ mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
+ }
mCallbacks.onDismiss();
}
});
@@ -606,6 +612,20 @@
dropInAnimation.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationCancel(Animator animation) {
+ mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ CUJ_TAKE_SCREENSHOT, mScreenshotPreview)
+ .setTag("DropIn");
+ mInteractionJankMonitor.begin(builder);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
if (DEBUG_ANIM) {
Log.d(TAG, "drop-in animation ended");
@@ -631,7 +651,7 @@
mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f);
requestLayout();
-
+ mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
createScreenshotActionsShadeAnimation().start();
}
});
@@ -702,9 +722,30 @@
mActionsContainer.setVisibility(View.VISIBLE);
mActionsContainerBackground.setVisibility(View.VISIBLE);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
+ .setTag("Actions")
+ .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+ mInteractionJankMonitor.begin(builder);
+ }
+ });
+
animator.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
- mBackgroundProtection.setAlpha(t);
float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
mActionsContainer.setAlpha(containerAlpha);
mActionsContainerBackground.setAlpha(containerAlpha);
@@ -910,7 +951,6 @@
}
mDismissButton.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
- mBackgroundProtection.setVisibility(View.GONE);
// set these invisible, but not gone, so that the views are laid out correctly
mActionsContainerBackground.setVisibility(View.INVISIBLE);
mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
@@ -932,7 +972,6 @@
mDismissButton.setVisibility(View.VISIBLE);
}
mActionsContainer.setVisibility(View.VISIBLE);
- mBackgroundProtection.setVisibility(View.VISIBLE);
mActionsContainerBackground.setVisibility(View.VISIBLE);
mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
mScreenshotPreview.setVisibility(View.VISIBLE);
@@ -969,7 +1008,6 @@
mPendingSharedTransition = false;
mActionsContainerBackground.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
- mBackgroundProtection.setAlpha(0f);
mDismissButton.setVisibility(View.GONE);
mScrollingScrim.setVisibility(View.GONE);
mScrollablePreview.setVisibility(View.GONE);
@@ -1016,7 +1054,6 @@
mDismissButton.setAlpha(alpha);
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
- mBackgroundProtection.setAlpha(alpha);
mScreenshotPreviewBorder.setAlpha(alpha);
});
alphaAnim.setDuration(600);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 9156601..71c2cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -72,6 +72,10 @@
mDefaultTimeout = timeout;
}
+ int getDefaultTimeoutMillis() {
+ return mDefaultTimeout;
+ }
+
/**
* Cancel the current timeout, if any. To reset the delayed runnable use resetTimeout instead.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 4d933d9..e44d334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -29,7 +29,6 @@
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
import java.util.ArrayList;
@@ -49,6 +48,7 @@
private TextView mTextView;
private NotificationEntry mShowingEntry;
private Runnable mOnDrawingRectChangedListener;
+ private boolean mRedactSensitiveContent;
public HeadsUpStatusBarView(Context context) {
this(context, null);
@@ -111,29 +111,28 @@
}
public void setEntry(NotificationEntry entry) {
- if (mShowingEntry != null) {
- mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
- }
mShowingEntry = entry;
-
if (mShowingEntry != null) {
CharSequence text = entry.headsUpStatusBarText;
- if (entry.isSensitive()) {
+ if (mRedactSensitiveContent && entry.hasSensitiveContents()) {
text = entry.headsUpStatusBarTextPublic;
}
mTextView.setText(text);
- mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
}
}
- private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
- if (entry != mShowingEntry) {
- throw new IllegalStateException("Got a sensitivity change for " + entry
- + " but mShowingEntry is " + mShowingEntry);
+ public void setRedactSensitiveContent(boolean redactSensitiveContent) {
+ if (mRedactSensitiveContent == redactSensitiveContent) {
+ return;
}
- // Update the text
- setEntry(entry);
- };
+ mRedactSensitiveContent = redactSensitiveContent;
+ if (mShowingEntry != null && mShowingEntry.hasSensitiveContents()) {
+ mTextView.setText(
+ mRedactSensitiveContent
+ ? mShowingEntry.headsUpStatusBarTextPublic
+ : mShowingEntry.headsUpStatusBarText);
+ }
+ }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c1ea6bf..cbe722b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -386,7 +386,7 @@
}
if (view is ExpandableNotificationRow) {
// Only drag down on sensitive views, otherwise the ExpandHelper will take this
- return view.entry.isSensitive
+ return lockScreenUserManager.notifNeedsRedactionInPublic(view.entry)
}
}
return false
@@ -552,7 +552,8 @@
logger.logShadeDisabledOnGoToLockedShade()
return
}
- var userId: Int = lockScreenUserManager.getCurrentUserId()
+ val currentUser = lockScreenUserManager.currentUserId
+ var userId: Int = currentUser
var entry: NotificationEntry? = null
if (expandView is ExpandableNotificationRow) {
entry = expandView.entry
@@ -562,12 +563,18 @@
entry.setGroupExpansionChanging(true)
userId = entry.sbn.userId
}
- var fullShadeNeedsBouncer = (!lockScreenUserManager.userAllowsPrivateNotificationsInPublic(
- lockScreenUserManager.getCurrentUserId()) ||
- !lockScreenUserManager.shouldShowLockscreenNotifications() ||
- falsingCollector.shouldEnforceBouncer())
- if (keyguardBypassController.bypassEnabled) {
- fullShadeNeedsBouncer = false
+ val fullShadeNeedsBouncer = when {
+ // No bouncer necessary if we're bypassing
+ keyguardBypassController.bypassEnabled -> false
+ // Redacted notificationss are present, bouncer should be shown before un-redacting in
+ // the full shade
+ lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(currentUser) -> true
+ // Notifications are hidden in public, bouncer should be shown before showing them in
+ // the full shade
+ !lockScreenUserManager.shouldShowLockscreenNotifications() -> true
+ // Bouncer is being enforced, so we need to show it
+ falsingCollector.shouldEnforceBouncer() -> true
+ else -> false
}
if (lockScreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
statusBarStateController.setLeaveOpenOnKeyguardHide(true)
@@ -911,4 +918,4 @@
host.getLocationOnScreen(temp2)
return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1])
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 9a1a144..5fd9671 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -71,17 +71,22 @@
boolean shouldHideNotifications(String key);
boolean shouldShowOnKeyguard(NotificationEntry entry);
+ void addOnNeedsRedactionInPublicChangedListener(Runnable listener);
+
+ void removeOnNeedsRedactionInPublicChangedListener(Runnable listener);
+
boolean isAnyProfilePublicMode();
void updatePublicMode();
- boolean needsRedaction(NotificationEntry entry);
+ /** Does this notification require redaction if it is displayed when the device is public? */
+ boolean notifNeedsRedactionInPublic(NotificationEntry entry);
/**
- * Has the given user chosen to allow their private (full) notifications to be shown even
- * when the lockscreen is in "public" (secure & locked) mode?
+ * Do all sensitive notifications belonging to the given user require redaction when they are
+ * displayed in public?
*/
- boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
+ boolean sensitiveNotifsNeedRedactionInPublic(int userId);
/**
* Has the given user chosen to allow notifications to be shown even when the lockscreen is in
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 56e09f0..334cfe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,7 +24,6 @@
import android.app.ActivityManager;
import android.app.KeyguardManager;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -45,7 +44,6 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -60,6 +58,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -85,13 +84,12 @@
private final DeviceProvisionedController mDeviceProvisionedController;
private final KeyguardStateController mKeyguardStateController;
private final SecureSettings mSecureSettings;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final Lazy<OverviewProxyService> mOverviewProxyService;
private final Object mLock = new Object();
-
- // Lazy
- private NotificationEntryManager mEntryManager;
-
private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
+ private final Lazy<NotificationEntryManager> mEntryManagerLazy;
private final DevicePolicyManager mDevicePolicyManager;
private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
@@ -103,13 +101,14 @@
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
-
- private boolean mShowLockscreenNotifications;
- private boolean mAllowLockscreenRemoteInput;
- private LockPatternUtils mLockPatternUtils;
- protected KeyguardManager mKeyguardManager;
- private int mState = StatusBarState.SHADE;
- private List<KeyguardNotificationSuppressor> mKeyguardSuppressors = new ArrayList<>();
+ private final LockPatternUtils mLockPatternUtils;
+ private final List<KeyguardNotificationSuppressor> mKeyguardSuppressors = new ArrayList<>();
+ protected final Context mContext;
+ private final Handler mMainHandler;
+ protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
+ protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
+ private final ListenerSet<Runnable> mOnSensitiveContentRedactionChangeListeners =
+ new ListenerSet<>();
protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
@Override
@@ -120,7 +119,11 @@
isCurrentProfile(getSendingUserId())) {
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
- getEntryManager().updateNotifications("ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED");
+ for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) {
+ listener.run();
+ }
+ mEntryManagerLazy.get()
+ .updateNotifications("ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED");
}
}
};
@@ -142,7 +145,7 @@
// The filtering needs to happen before the update call below in order to
// make sure
// the presenter has the updated notifications from the new user
- getEntryManager().reapplyFilterAndSort("user switched");
+ mEntryManagerLazy.get().reapplyFilterAndSort("user switched");
mPresenter.onUserSwitched(mCurrentUserId);
for (UserChangedListener listener : mListeners) {
@@ -156,7 +159,7 @@
break;
case Intent.ACTION_USER_UNLOCKED:
// Start the overview connection to the launcher service
- Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ mOverviewProxyService.get().startConnectionToCurrentUser();
break;
case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
final IntentSender intentSender = intent.getParcelableExtra(
@@ -179,28 +182,26 @@
}
};
- protected final Context mContext;
- private final Handler mMainHandler;
- protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
- protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
-
- protected int mCurrentUserId = 0;
+ // Late-init
protected NotificationPresenter mPresenter;
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
- private boolean mHideSilentNotificationsOnLockscreen;
+ protected KeyguardManager mKeyguardManager;
- private NotificationEntryManager getEntryManager() {
- if (mEntryManager == null) {
- mEntryManager = Dependency.get(NotificationEntryManager.class);
- }
- return mEntryManager;
- }
+ protected int mCurrentUserId = 0;
+ private int mState = StatusBarState.SHADE;
+ private boolean mHideSilentNotificationsOnLockscreen;
+ private boolean mShowLockscreenNotifications;
+ private boolean mAllowLockscreenRemoteInput;
@Inject
- public NotificationLockscreenUserManagerImpl(Context context,
+ public NotificationLockscreenUserManagerImpl(
+ Context context,
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ Lazy<NotificationEntryManager> notificationEntryManagerLazy,
+ Lazy<OverviewProxyService> overviewProxyServiceLazy,
UserManager userManager,
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
@@ -216,9 +217,11 @@
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
+ mOverviewProxyService = overviewProxyServiceLazy;
mCurrentUserId = ActivityManager.getCurrentUser();
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
+ mEntryManagerLazy = notificationEntryManagerLazy;
mClickNotifier = clickNotifier;
statusBarStateController.addCallback(this);
mLockPatternUtils = new LockPatternUtils(context);
@@ -227,10 +230,12 @@
mDeviceProvisionedController = deviceProvisionedController;
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
dumpManager.registerDumpable(this);
}
+ @Override
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
@@ -243,7 +248,10 @@
mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
updateLockscreenNotificationSetting();
- getEntryManager().updateNotifications("LOCK_SCREEN_SHOW_NOTIFICATIONS,"
+ for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) {
+ listener.run();
+ }
+ mEntryManagerLazy.get().updateNotifications("LOCK_SCREEN_SHOW_NOTIFICATIONS,"
+ " or LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS change");
}
};
@@ -253,7 +261,7 @@
public void onChange(boolean selfChange) {
updateLockscreenNotificationSetting();
if (mDeviceProvisionedController.isDeviceProvisioned()) {
- getEntryManager().updateNotifications("LOCK_SCREEN_ALLOW_REMOTE_INPUT"
+ mEntryManagerLazy.get().updateNotifications("LOCK_SCREEN_ALLOW_REMOTE_INPUT"
+ " or ZEN_MODE change");
}
}
@@ -312,14 +320,17 @@
mSettingsObserver.onChange(false); // set up
}
+ @Override
public boolean shouldShowLockscreenNotifications() {
return mShowLockscreenNotifications;
}
+ @Override
public boolean shouldAllowLockscreenRemoteInput() {
return mAllowLockscreenRemoteInput;
}
+ @Override
public boolean isCurrentProfile(int userId) {
synchronized (mLock) {
return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
@@ -334,7 +345,7 @@
if (userId == UserHandle.USER_ALL) {
userId = mCurrentUserId;
}
- boolean inLockdown = Dependency.get(KeyguardUpdateMonitor.class).isUserInLockdown(userId);
+ boolean inLockdown = mKeyguardUpdateMonitor.isUserInLockdown(userId);
mUsersInLockdownLatestResult.put(userId, inLockdown);
return inLockdown;
}
@@ -343,6 +354,7 @@
* Returns true if we're on a secure lockscreen and the user wants to hide notification data.
* If so, notifications should be hidden.
*/
+ @Override
public boolean shouldHideNotifications(int userId) {
boolean hide = isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
|| (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId))
@@ -355,6 +367,7 @@
* Returns true if we're on a secure lockscreen and the user wants to hide notifications via
* package-specific override.
*/
+ @Override
public boolean shouldHideNotifications(String key) {
if (mCommonNotifCollectionLazy.get() == null) {
Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
@@ -365,6 +378,7 @@
&& visibleEntry.getRanking().getLockscreenVisibilityOverride() == VISIBILITY_SECRET;
}
+ @Override
public boolean shouldShowOnKeyguard(NotificationEntry entry) {
if (mCommonNotifCollectionLazy.get() == null) {
Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
@@ -387,14 +401,6 @@
return mShowLockscreenNotifications && exceedsPriorityThreshold;
}
- private void setShowLockscreenNotifications(boolean show) {
- mShowLockscreenNotifications = show;
- }
-
- private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
- mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
- }
-
protected void updateLockscreenNotificationSetting() {
final boolean show = mSecureSettings.getIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
@@ -408,7 +414,7 @@
mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0;
- setShowLockscreenNotifications(show && allowedByDpm);
+ mShowLockscreenNotifications = show && allowedByDpm;
if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
final boolean remoteInput = mSecureSettings.getIntForUser(
@@ -418,9 +424,9 @@
final boolean remoteInputDpm =
(dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
- setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
+ mAllowLockscreenRemoteInput = remoteInput && remoteInputDpm;
} else {
- setLockscreenAllowRemoteInput(false);
+ mAllowLockscreenRemoteInput = false;
}
}
@@ -428,7 +434,7 @@
* Has the given user chosen to allow their private (full) notifications to be shown even
* when the lockscreen is in "public" (secure & locked) mode?
*/
- public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+ protected boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
if (userHandle == UserHandle.USER_ALL) {
return true;
}
@@ -473,10 +479,12 @@
/**
* Save the current "public" (locked and secure) state of the lockscreen.
*/
+ @Override
public void setLockscreenPublicMode(boolean publicMode, int userId) {
mLockscreenPublicMode.put(userId, publicMode);
}
+ @Override
public boolean isLockscreenPublicMode(int userId) {
if (userId == UserHandle.USER_ALL) {
return mLockscreenPublicMode.get(mCurrentUserId, false);
@@ -493,6 +501,7 @@
* Has the given user chosen to allow notifications to be shown even when the lockscreen is in
* "public" (secure & locked) mode?
*/
+ @Override
public boolean userAllowsNotificationsInPublic(int userHandle) {
if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
return true;
@@ -513,36 +522,37 @@
}
/** @return true if the entry needs redaction when on the lockscreen. */
- public boolean needsRedaction(NotificationEntry ent) {
+ @Override
+ public boolean notifNeedsRedactionInPublic(NotificationEntry ent) {
int userId = ent.getSbn().getUserId();
+ return ent.hasSensitiveContents() && sensitiveNotifsNeedRedactionInPublic(userId);
+ }
+ @Override
+ public boolean sensitiveNotifsNeedRedactionInPublic(int userId) {
boolean isCurrentUserRedactingNotifs =
!userAllowsPrivateNotificationsInPublic(mCurrentUserId);
+ if (userId == mCurrentUserId) {
+ return isCurrentUserRedactingNotifs;
+ }
+
boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId);
boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId);
// redact notifications if the current user is redacting notifications; however if the
// notification is associated with a managed profile, we rely on the managed profile
// setting to determine whether to redact it
- boolean isNotifRedacted = (!isNotifForManagedProfile && isCurrentUserRedactingNotifs)
- || isNotifUserRedacted;
-
- boolean notificationRequestsRedaction =
- ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE;
- boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
-
- return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ return (!isNotifForManagedProfile && isCurrentUserRedactingNotifs) || isNotifUserRedacted;
}
- private boolean packageHasVisibilityOverride(String key) {
- if (mCommonNotifCollectionLazy.get() == null) {
- Log.wtf(TAG, "mEntryManager was null!", new Throwable());
- return true;
- }
- NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
- return entry != null
- && entry.getRanking().getLockscreenVisibilityOverride()
- == Notification.VISIBILITY_PRIVATE;
+ @Override
+ public void addOnNeedsRedactionInPublicChangedListener(Runnable listener) {
+ mOnSensitiveContentRedactionChangeListeners.addIfAbsent(listener);
+ }
+
+ @Override
+ public void removeOnNeedsRedactionInPublicChangedListener(Runnable listener) {
+ mOnSensitiveContentRedactionChangeListeners.remove(listener);
}
private void updateCurrentProfilesCache() {
@@ -562,12 +572,16 @@
for (UserChangedListener listener : mListeners) {
listener.onCurrentProfilesChanged(mCurrentProfiles);
}
+ for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) {
+ listener.run();
+ }
});
}
/**
* If any of the profiles are in public mode.
*/
+ @Override
public boolean isAnyProfilePublicMode() {
synchronized (mLock) {
for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
@@ -596,10 +610,12 @@
/**
* Returns the current user id. This can change if the user is switched.
*/
+ @Override
public int getCurrentUserId() {
return mCurrentUserId;
}
+ @Override
public SparseArray<UserInfo> getCurrentProfiles() {
return mCurrentProfiles;
}
@@ -640,7 +656,8 @@
setLockscreenPublicMode(isProfilePublic, userId);
mUsersWithSeparateWorkChallenge.put(userId, needsSeparateChallenge);
}
- getEntryManager().updateNotifications("NotificationLockscreenUserManager.updatePublicMode");
+ mEntryManagerLazy.get()
+ .updateNotifications("NotificationLockscreenUserManager.updatePublicMode");
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 734bc48..0d60401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -326,9 +326,9 @@
|| child.isPinned();
boolean isLastChild = child == lastChild;
final float viewStart = child.getTranslationY();
-
- final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
- expandingAnimated, isLastChild);
+ final float shelfClipStart = getTranslationY() - mPaddingBetweenElements;
+ final float inShelfAmount = getAmountInShelf(i, child, scrollingFast,
+ expandingAnimated, isLastChild, shelfClipStart);
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
@@ -609,10 +609,18 @@
}
/**
- * @return the amount how much this notification is in the shelf
+ * @param i Index of the view in the host layout.
+ * @param view The current ExpandableView.
+ * @param scrollingFast Whether we are scrolling fast.
+ * @param expandingAnimated Whether we are expanding a notification.
+ * @param isLastChild Whether this is the last view.
+ * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+ * @return The amount how much this notification is in the shelf.
+ * 0f is not in shelf. 1f is completely in shelf.
*/
- private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
- boolean expandingAnimated, boolean isLastChild) {
+ @VisibleForTesting
+ public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
+ boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
// Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY();
@@ -635,29 +643,33 @@
float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f;
- float shelfStart = getTranslationY() - mPaddingBetweenElements;
+
+ // Don't animate shelf icons during shade expansion.
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
// TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
fullTransitionAmount = 1f;
iconTransitionAmount = 1f;
}
- } else if (viewEnd >= shelfStart
+
+ } else if (viewEnd >= shelfClipStart
&& (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
&& (mAmbientState.isShadeExpanded()
|| (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
- if (viewStart < shelfStart) {
- float fullAmount = (shelfStart - viewStart) / fullHeight;
+ if (viewStart < shelfClipStart && Math.abs(viewStart - shelfClipStart) > 0.001f) {
+ // Partially clipped by shelf.
+ float fullAmount = (shelfClipStart - viewStart) / fullHeight;
fullAmount = Math.min(1.0f, fullAmount);
fullTransitionAmount = 1.0f - fullAmount;
if (isLastChild) {
// Reduce icon transform distance to completely fade in shelf icon
// by the time the notification icon fades out, and vice versa
- iconTransitionAmount = (shelfStart - viewStart)
+ iconTransitionAmount = (shelfClipStart - viewStart)
/ (iconTransformStart - viewStart);
} else {
- iconTransitionAmount = (shelfStart - iconTransformStart) / transformDistance;
+ iconTransitionAmount = (shelfClipStart - iconTransformStart)
+ / transformDistance;
}
iconTransitionAmount = MathUtils.constrain(iconTransitionAmount, 0.0f, 1.0f);
iconTransitionAmount = 1.0f - iconTransitionAmount;
@@ -772,6 +784,9 @@
}
private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
+ if (mShelfIcons == null) {
+ return null;
+ }
return mShelfIcons.getIconState(icon);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 054543c..30cb09d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -53,6 +53,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Stack;
@@ -207,12 +208,9 @@
|| !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
userPublic = false;
}
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+ boolean needsRedaction = mLockscreenUserManager.notifNeedsRedactionInPublic(ent);
boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
- currentUserId);
- ent.setSensitive(sensitive, deviceSensitive);
+ ent.getRow().setSensitive(sensitive);
ent.getRow().setNeedsRedaction(needsRedaction);
mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
@@ -365,6 +363,8 @@
boolean hasClearableAlertingNotifs = false;
boolean hasNonClearableSilentNotifs = false;
boolean hasClearableSilentNotifs = false;
+ HashSet<Integer> clearableAlertingSensitiveNotifUsers = new HashSet<>();
+ HashSet<Integer> clearableSilentSensitiveNotifUsers = new HashSet<>();
final int childCount = mListContainer.getContainerChildCount();
int visibleTopLevelEntries = 0;
for (int i = 0; i < childCount; i++) {
@@ -376,10 +376,11 @@
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
+ NotificationEntry entry = row.getEntry();
+ boolean isSilent = entry.getBucket() == BUCKET_SILENT;
// NOTE: NotificationEntry.isClearable() will internally check group children to ensure
// the group itself definitively clearable.
- boolean isClearable = row.getEntry().isClearable();
+ boolean isClearable = entry.isClearable();
visibleTopLevelEntries++;
if (isSilent) {
if (isClearable) {
@@ -394,13 +395,24 @@
hasNonClearableAlertingNotifs = true;
}
}
+ if (isClearable && entry.hasSensitiveContents()) {
+ int userId = entry.getSbn().getUserId();
+ if (isSilent) {
+ clearableSilentSensitiveNotifUsers.add(userId);
+ } else {
+ clearableAlertingSensitiveNotifUsers.add(userId);
+ }
+ }
}
mStackController.setNotifStats(new NotifStats(
visibleTopLevelEntries /* numActiveNotifs */,
hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
- hasClearableSilentNotifs /* hasClearableSilentNotifs */
+ hasClearableSilentNotifs /* hasClearableSilentNotifs */,
+ clearableAlertingSensitiveNotifUsers /* clearableAlertingSensitiveNotifUsers */,
+ clearableSilentSensitiveNotifUsers /* clearableSilentSensitiveNotifUsers */
+
));
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index a0ccd57..d16e9e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -80,7 +80,7 @@
@VisibleForTesting
boolean isDynamicPrivacyEnabled() {
- return !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ return mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(
mLockscreenUserManager.getCurrentUserId());
}
@@ -95,6 +95,10 @@
mListeners.add(listener);
}
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
/**
* Is the notification shade currently in a locked down mode where it's fully showing but the
* contents aren't revealed yet?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4fc347a..e3c39dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -169,9 +169,6 @@
*/
private boolean hasSentReply;
- private boolean mSensitive = true;
- private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();
-
private boolean mAutoHeadsUp;
private boolean mPulseSupressed;
private int mBucket = BUCKET_ALERTING;
@@ -867,33 +864,29 @@
}
/**
- * Set this notification to be sensitive.
- *
- * @param sensitive true if the content of this notification is sensitive right now
- * @param deviceSensitive true if the device in general is sensitive right now
+ * Returns the visibility of this notification on the lockscreen, taking into account both the
+ * notification's defined visibility, as well as the visibility override as determined by the
+ * device policy.
*/
- public void setSensitive(boolean sensitive, boolean deviceSensitive) {
- getRow().setSensitive(sensitive, deviceSensitive);
- if (sensitive != mSensitive) {
- mSensitive = sensitive;
- for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
- mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
- }
+ public int getLockscreenVisibility() {
+ int setting = mRanking.getLockscreenVisibilityOverride();
+ if (setting == Ranking.VISIBILITY_NO_OVERRIDE) {
+ setting = mSbn.getNotification().visibility;
}
+ return setting;
}
- public boolean isSensitive() {
- return mSensitive;
- }
-
- /** Add a listener to be notified when the entry's sensitivity changes. */
- public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
- mOnSensitivityChangedListeners.add(listener);
- }
-
- /** Remove a listener that was registered above. */
- public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
- mOnSensitivityChangedListeners.remove(listener);
+ /**
+ * Does this notification contain sensitive content? If the user's settings specify, then this
+ * content would need to be redacted when the device this public.
+ *
+ * NOTE: If the notification's visibility setting is VISIBILITY_SECRET, then this will return
+ * false; SECRET notifications are omitted entirely when the device is public, so effectively
+ * the contents of the notification are not sensitive whenever the notification is actually
+ * visible.
+ */
+ public boolean hasSensitiveContents() {
+ return getLockscreenVisibility() == Notification.VISIBILITY_PRIVATE;
}
public boolean isPulseSuppressed() {
@@ -954,12 +947,6 @@
}
}
- /** Listener interface for {@link #addOnSensitivityChangedListener} */
- public interface OnSensitivityChangedListener {
- /** Called when the sensitivity changes */
- void onSensitivityChanged(@NonNull NotificationEntry entry);
- }
-
/** @see #getDismissState() */
public enum DismissState {
/** User has not dismissed this notif or its parent */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 3516625..b24d292 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -56,7 +56,6 @@
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
viewConfigCoordinator: ViewConfigCoordinator,
visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
activityLaunchAnimCoordinator: ActivityLaunchAnimCoordinator
) : NotifCoordinators {
@@ -94,7 +93,6 @@
mCoordinators.add(shadeEventCoordinator)
mCoordinators.add(viewConfigCoordinator)
mCoordinators.add(visualStabilityCoordinator)
- mCoordinators.add(sensitiveContentCoordinator)
mCoordinators.add(activityLaunchAnimCoordinator)
if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
mCoordinators.add(smartspaceDedupingCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 57fd197..56484010 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -29,13 +28,11 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import java.util.Collections;
-import java.util.List;
import javax.inject.Inject;
@@ -53,10 +50,10 @@
private final HighPriorityProvider mHighPriorityProvider;
private final SectionClassifier mSectionClassifier;
private final NodeController mSilentNodeController;
- private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
- private boolean mHasSilentEntries;
- private boolean mHasMinimizedEntries;
+ private final AlertingNotifSectioner mAlertingNotifSectioner = new AlertingNotifSectioner();
+ private final SilentNotifSectioner mSilentNotifSectioner = new SilentNotifSectioner();
+ private final MinimizedNotifSectioner mMinimizedNotifSectioner = new MinimizedNotifSectioner();
@Inject
public RankingCoordinator(
@@ -64,14 +61,12 @@
HighPriorityProvider highPriorityProvider,
SectionClassifier sectionClassifier,
@AlertingHeader NodeController alertingHeaderController,
- @SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
mSectionClassifier = sectionClassifier;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
- mSilentHeaderController = silentHeaderController;
}
@Override
@@ -95,82 +90,6 @@
return mMinimizedNotifSectioner;
}
- private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting",
- NotificationPriorityBucketKt.BUCKET_ALERTING) {
- @Override
- public boolean isInSection(ListEntry entry) {
- return mHighPriorityProvider.isHighPriority(entry);
- }
-
- @Nullable
- @Override
- public NodeController getHeaderNodeController() {
- // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController
- if (SHOW_ALL_SECTIONS) {
- return mAlertingHeaderController;
- }
- return null;
- }
- };
-
- private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent",
- NotificationPriorityBucketKt.BUCKET_SILENT) {
- @Override
- public boolean isInSection(ListEntry entry) {
- return !mHighPriorityProvider.isHighPriority(entry)
- && !entry.getRepresentativeEntry().isAmbient();
- }
-
- @Nullable
- @Override
- public NodeController getHeaderNodeController() {
- return mSilentNodeController;
- }
-
- @Nullable
- @Override
- public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
- mHasSilentEntries = false;
- for (int i = 0; i < entries.size(); i++) {
- if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
- mHasSilentEntries = true;
- break;
- }
- }
- mSilentHeaderController.setClearSectionEnabled(
- mHasSilentEntries | mHasMinimizedEntries);
- }
- };
-
- private final NotifSectioner mMinimizedNotifSectioner = new NotifSectioner("Minimized",
- NotificationPriorityBucketKt.BUCKET_SILENT) {
- @Override
- public boolean isInSection(ListEntry entry) {
- return !mHighPriorityProvider.isHighPriority(entry)
- && entry.getRepresentativeEntry().isAmbient();
- }
-
- @Nullable
- @Override
- public NodeController getHeaderNodeController() {
- return mSilentNodeController;
- }
-
- @Nullable
- @Override
- public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
- mHasMinimizedEntries = false;
- for (int i = 0; i < entries.size(); i++) {
- if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
- mHasMinimizedEntries = true;
- break;
- }
- }
- mSilentHeaderController.setClearSectionEnabled(
- mHasSilentEntries | mHasMinimizedEntries);
- }
- };
-
/**
* Checks whether to filter out the given notification based the notification's Ranking object.
* NotifListBuilder invalidates the notification list each time the ranking is updated,
@@ -202,4 +121,64 @@
mDndVisualEffectsFilter.invalidateList();
}
};
+
+ private class AlertingNotifSectioner extends NotifSectioner {
+
+ AlertingNotifSectioner() {
+ super("Alerting", NotificationPriorityBucketKt.BUCKET_ALERTING);
+ }
+
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return mHighPriorityProvider.isHighPriority(entry);
+ }
+
+ @Nullable
+ @Override
+ public NodeController getHeaderNodeController() {
+ // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController
+ if (SHOW_ALL_SECTIONS) {
+ return mAlertingHeaderController;
+ }
+ return null;
+ }
+ }
+
+ private class SilentNotifSectioner extends NotifSectioner {
+
+ SilentNotifSectioner() {
+ super("Silent", NotificationPriorityBucketKt.BUCKET_SILENT);
+ }
+
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return !mHighPriorityProvider.isHighPriority(entry)
+ && !entry.getRepresentativeEntry().isAmbient();
+ }
+
+ @Nullable
+ @Override
+ public NodeController getHeaderNodeController() {
+ return mSilentNodeController;
+ }
+ }
+
+ private class MinimizedNotifSectioner extends NotifSectioner {
+
+ MinimizedNotifSectioner() {
+ super("Minimized", NotificationPriorityBucketKt.BUCKET_SILENT);
+ }
+
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return !mHighPriorityProvider.isHighPriority(entry)
+ && entry.getRepresentativeEntry().isAmbient();
+ }
+
+ @Nullable
+ @Override
+ public NodeController getHeaderNodeController() {
+ return mSilentNodeController;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
deleted file mode 100644
index 3f8a39f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2021 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.statusbar.notification.collection.coordinator
-
-import android.os.UserHandle
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.DynamicPrivacyController
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-
-@Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
-interface SensitiveContentCoordinatorModule
-
-@Module
-private interface PrivateSensitiveContentCoordinatorModule {
- @Binds
- fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
-}
-
-/** Coordinates re-inflation and post-processing of sensitive notification content. */
-interface SensitiveContentCoordinator : Coordinator
-
-@CoordinatorScope
-private class SensitiveContentCoordinatorImpl @Inject constructor(
- private val dynamicPrivacyController: DynamicPrivacyController,
- private val lockscreenUserManager: NotificationLockscreenUserManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val statusBarStateController: StatusBarStateController,
- private val keyguardStateController: KeyguardStateController
-) : Invalidator("SensitiveContentInvalidator"),
- SensitiveContentCoordinator,
- DynamicPrivacyController.Listener,
- OnBeforeRenderListListener {
-
- override fun attach(pipeline: NotifPipeline) {
- dynamicPrivacyController.addListener(this)
- pipeline.addOnBeforeRenderListListener(this)
- pipeline.addPreRenderInvalidator(this)
- }
-
- override fun onDynamicPrivacyChanged(): Unit = invalidateList()
-
- override fun onBeforeRenderList(entries: List<ListEntry>) {
- if (keyguardStateController.isKeyguardGoingAway() ||
- statusBarStateController.getState() == StatusBarState.KEYGUARD &&
- keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- KeyguardUpdateMonitor.getCurrentUser())) {
- // don't update yet if:
- // - the keyguard is currently going away
- // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
-
- // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
- // dependent state changes invalidate the pipeline
- return
- }
-
- val currentUserId = lockscreenUserManager.currentUserId
- val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
- val deviceSensitive = devicePublic &&
- !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
- val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
- for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
- val notifUserId = entry.sbn.user.identifier
- val userLockscreen = devicePublic ||
- lockscreenUserManager.isLockscreenPublicMode(notifUserId)
- val userPublic = when {
- // if we're not on the lockscreen, we're definitely private
- !userLockscreen -> false
- // we are on the lockscreen, so unless we're dynamically unlocked, we're
- // definitely public
- !dynamicallyUnlocked -> true
- // we're dynamically unlocked, but check if the notification needs
- // a separate challenge if it's from a work profile
- else -> when (notifUserId) {
- currentUserId -> false
- UserHandle.USER_ALL -> false
- else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
- }
- }
- val needsRedaction = lockscreenUserManager.needsRedaction(entry)
- val isSensitive = userPublic && needsRedaction
- entry.setSensitive(isSensitive, deviceSensitive)
- }
- }
-}
-
-private fun extractAllRepresentativeEntries(
- entries: List<ListEntry>
-): Sequence<NotificationEntry> =
- entries.asSequence().flatMap(::extractAllRepresentativeEntries)
-
-private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
- sequence {
- listEntry.representativeEntry?.let { yield(it) }
- if (listEntry is GroupEntry) {
- yieldAll(extractAllRepresentativeEntries(listEntry.children))
- }
- }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 1c96e8c..2374e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -50,6 +50,8 @@
var hasClearableAlertingNotifs = false
var hasNonClearableSilentNotifs = false
var hasClearableSilentNotifs = false
+ val clearableAlertingSensitiveNotifUsers = mutableSetOf<Int>()
+ val clearableSilentSensitiveNotifUsers = mutableSetOf<Int>()
entries.forEach {
val section = checkNotNull(it.section) { "Null section for ${it.key}" }
val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" }
@@ -63,13 +65,22 @@
!isSilent && isClearable -> hasClearableAlertingNotifs = true
!isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
}
+ if (isClearable && entry.hasSensitiveContents()) {
+ if (isSilent) {
+ clearableSilentSensitiveNotifUsers.add(entry.sbn.userId)
+ } else {
+ clearableAlertingSensitiveNotifUsers.add(entry.sbn.userId)
+ }
+ }
}
return NotifStats(
numActiveNotifs = entries.size,
hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
hasClearableAlertingNotifs = hasClearableAlertingNotifs,
hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
- hasClearableSilentNotifs = hasClearableSilentNotifs
+ hasClearableSilentNotifs = hasClearableSilentNotifs,
+ clearableAlertingSensitiveNotifUsers = clearableAlertingSensitiveNotifUsers,
+ clearableSilentSensitiveNotifUsers = clearableSilentSensitiveNotifUsers
)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
index 274affd..8ecffcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -20,7 +20,6 @@
import com.android.systemui.statusbar.notification.collection.coordinator.ActivityLaunchAnimCoordinatorModule
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl
-import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -50,7 +49,6 @@
@Module(includes = [
ActivityLaunchAnimCoordinatorModule::class,
- SensitiveContentCoordinatorModule::class,
])
private abstract class InternalCoordinatorsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 4c7b2bb..6e96aad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.inflation;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+
import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
@@ -249,10 +251,13 @@
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseLowPriority(isLowPriority);
-
- // TODO: Replace this API with RowContentBindParams directly. Also move to a separate
- // redaction controller.
- row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
+ boolean needsRedaction =
+ mNotificationLockscreenUserManager.notifNeedsRedactionInPublic(entry);
+ if (needsRedaction) {
+ params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
+ } else {
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+ }
params.rebindAllContentViews();
mRowContentBindStage.requestRebind(entry, en -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index b6278d1..580d853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -28,11 +28,21 @@
val hasNonClearableAlertingNotifs: Boolean,
val hasClearableAlertingNotifs: Boolean,
val hasNonClearableSilentNotifs: Boolean,
- val hasClearableSilentNotifs: Boolean
+ val hasClearableSilentNotifs: Boolean,
+ val clearableAlertingSensitiveNotifUsers: Set<Int>,
+ val clearableSilentSensitiveNotifUsers: Set<Int>
) {
companion object {
@JvmStatic
- val empty = NotifStats(0, false, false, false, false)
+ val empty = NotifStats(
+ numActiveNotifs = 0,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = false,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = false,
+ clearableAlertingSensitiveNotifUsers = emptySet(),
+ clearableSilentSensitiveNotifUsers = emptySet(),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 2a9cfd0..f58918f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -182,4 +182,4 @@
@Scope
@Retention(AnnotationRetention.BINARY)
-annotation class SectionHeaderScope
\ No newline at end of file
+annotation class SectionHeaderScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index d896541..015e3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,6 +28,7 @@
import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -49,31 +50,29 @@
class IconManager @Inject constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
- private val iconBuilder: IconBuilder
+ private val iconBuilder: IconBuilder,
+ private val notifLockscreenUserManager: NotificationLockscreenUserManager
) : ConversationIconManager {
private var unimportantConversationKeys: Set<String> = emptySet()
fun attach() {
notifCollection.addCollectionListener(entryListener)
+ notifLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener(sensitivityListener)
}
private val entryListener = object : NotifCollectionListener {
- override fun onEntryInit(entry: NotificationEntry) {
- entry.addOnSensitivityChangedListener(sensitivityListener)
- }
-
- override fun onEntryCleanUp(entry: NotificationEntry) {
- entry.removeOnSensitivityChangedListener(sensitivityListener)
- }
-
override fun onRankingApplied() {
// rankings affect whether a conversation is important, which can change the icons
recalculateForImportantConversationChange()
}
}
- private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
- entry -> updateIconsSafe(entry)
+ private val sensitivityListener = Runnable {
+ for (entry in notifCollection.allNotifs) {
+ if (entry.hasSensitiveContents()) {
+ updateIconsSafe(entry)
+ }
+ }
}
private fun recalculateForImportantConversationChange() {
@@ -182,12 +181,16 @@
}
}
+ private inline val NotificationEntry.needsRedactionInPublic: Boolean get() =
+ hasSensitiveContents() &&
+ notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(sbn.userId)
+
@Throws(InflationException::class)
private fun getIconDescriptors(
entry: NotificationEntry
): Pair<StatusBarIcon, StatusBarIcon> {
val iconDescriptor = getIconDescriptor(entry, false /* redact */)
- val sensitiveDescriptor = if (entry.isSensitive) {
+ val sensitiveDescriptor = if (entry.needsRedactionInPublic) {
getIconDescriptor(entry, true /* redact */)
} else {
iconDescriptor
@@ -310,7 +313,7 @@
iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon)
return isImportantConversation(entry) && !isSmallIcon &&
- (!usedInSensitiveContext || !entry.isSensitive)
+ (!usedInSensitiveContext || !entry.needsRedactionInPublic)
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
@@ -338,4 +341,4 @@
* of a group from which the priority notification has been removed.
*/
fun setUnimportantConversations(keys: Collection<String>)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 6b79680..f0b221d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -160,8 +160,7 @@
val channels = groupList
.flatMap { group ->
group.channels.asSequence().filterNot { channel ->
- channel.isImportanceLockedByOEM ||
- channel.importance == IMPORTANCE_NONE ||
+ channel.importance == IMPORTANCE_NONE ||
channel.isImportanceLockedByCriticalDeviceFunction
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0ae3653..ed69e06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -93,8 +93,8 @@
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -206,7 +206,6 @@
/** Are we showing the "public" version */
private boolean mShowingPublic;
private boolean mSensitive;
- private boolean mSensitiveHiddenInGeneral;
private boolean mShowingPublicInitialized;
private boolean mHideSensitiveForIntrinsicHeight;
private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT;
@@ -376,44 +375,24 @@
* calls.
*/
private static Boolean isSystemNotification(Context context, StatusBarNotification sbn) {
- // TODO (b/194833441): clean up before launch
- if (Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1) {
- INotificationManager iNm = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ INotificationManager iNm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- boolean isSystem = false;
- try {
- isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId());
- } catch (RemoteException e) {
- Log.e(TAG, "cannot reach NMS");
- }
- RoleManager rm = context.getSystemService(RoleManager.class);
- List<String> fixedRoleHolders = new ArrayList<>();
- fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER));
- fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY));
- if (fixedRoleHolders.contains(sbn.getPackageName())) {
- isSystem = true;
- }
-
- return isSystem;
- } else {
- PackageManager packageManager = CentralSurfaces.getPackageManagerForUser(
- context, sbn.getUser().getIdentifier());
- Boolean isSystemNotification = null;
-
- try {
- PackageInfo packageInfo = packageManager.getPackageInfo(
- sbn.getPackageName(), PackageManager.GET_SIGNATURES);
-
- isSystemNotification =
- com.android.settingslib.Utils.isSystemPackage(
- context.getResources(), packageManager, packageInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "cacheIsSystemNotification: Could not find package info");
- }
- return isSystemNotification;
+ boolean isSystem = false;
+ try {
+ isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "cannot reach NMS");
}
+ RoleManager rm = context.getSystemService(RoleManager.class);
+ List<String> fixedRoleHolders = new ArrayList<>();
+ fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER));
+ fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY));
+ if (fixedRoleHolders.contains(sbn.getPackageName())) {
+ isSystem = true;
+ }
+
+ return isSystem;
}
public NotificationContentView[] getLayouts() {
@@ -567,9 +546,7 @@
mEntry.mIsSystemNotification = isSystemNotification(mContext, mEntry.getSbn());
}
- // TODO (b/194833441): remove when we've migrated to permission
- boolean isNonblockable = mEntry.getChannel().isImportanceLockedByOEM()
- || mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
+ boolean isNonblockable = mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
if (mEntry.mIsSystemNotification) {
@@ -1560,6 +1537,7 @@
mUseIncreasedHeadsUpHeight = use;
}
+ // TODO: remove this method and mNeedsRedaction entirely once the old pipeline is gone
public void setNeedsRedaction(boolean needsRedaction) {
// TODO: Move inflation logic out of this call and remove this method
if (mNeedsRedaction != needsRedaction) {
@@ -2644,9 +2622,8 @@
getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
}
- public void setSensitive(boolean sensitive, boolean hideSensitive) {
+ public void setSensitive(boolean sensitive) {
mSensitive = sensitive;
- mSensitiveHiddenInGeneral = hideSensitive;
}
@Override
@@ -2736,7 +2713,15 @@
* see {@link NotificationEntry#isDismissable()}.
*/
public boolean canViewBeDismissed() {
- return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ // Entry not dismissable.
+ if (!mEntry.isDismissable()) {
+ return false;
+ }
+ // Entry shouldn't be showing the public layout, it can be dismissed.
+ if (!shouldShowPublic()) {
+ return true;
+ }
+ return false;
}
/**
@@ -2745,7 +2730,7 @@
* clearability see {@link NotificationEntry#isClearable()}.
*/
public boolean canViewBeCleared() {
- return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ return mEntry.isClearable() && !shouldShowPublic();
}
private boolean shouldShowPublic() {
@@ -3509,10 +3494,28 @@
pw.print(", translation: " + getTranslation());
pw.print(", removed: " + isRemoved());
pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
- NotificationContentView showingLayout = getShowingLayout();
- pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
- pw.println();
- showingLayout.dump(pw, args);
+ pw.print(", sensitive: " + mSensitive);
+ pw.print(", hideSensitiveForIntrinsicHeight: " + mHideSensitiveForIntrinsicHeight);
+ pw.println(", privateShowing: " + !shouldShowPublic());
+ pw.print("privateLayout: ");
+ if (mPrivateLayout != null) {
+ pw.println();
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ mPrivateLayout.dump(pw, args);
+ mPrivateLayout.dumpSmartReplies(pw);
+ });
+ } else {
+ pw.println("null");
+ }
+ pw.print("publicLayout: ");
+ if (mPublicLayout != null) {
+ pw.println();
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ mPublicLayout.dump(pw, args);
+ });
+ } else {
+ pw.println("null");
+ }
if (getViewState() != null) {
getViewState().dump(pw, args);
@@ -3538,8 +3541,6 @@
}
pw.decreaseIndent();
pw.println("}");
- } else if (mPrivateLayout != null) {
- mPrivateLayout.dumpSmartReplies(pw);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 599039d..a60026c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -35,6 +35,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -69,6 +70,7 @@
public class ExpandableNotificationRowController implements NotifViewController {
private static final String TAG = "NotifRowController";
private final ExpandableNotificationRow mView;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
private final NotificationListContainer mListContainer;
private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
private final ActivatableNotificationViewController mActivatableNotificationViewController;
@@ -86,7 +88,6 @@
private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
private final StatusBarStateController mStatusBarStateController;
private final MetricsLogger mMetricsLogger;
-
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
@@ -100,12 +101,12 @@
private final Optional<BubblesManager> mBubblesManagerOptional;
private final SmartReplyConstants mSmartReplyConstants;
private final SmartReplyController mSmartReplyController;
-
private final ExpandableNotificationRowDragController mDragController;
@Inject
public ExpandableNotificationRowController(
ExpandableNotificationRow view,
+ NotificationLockscreenUserManager lockscreenUserManager,
ActivatableNotificationViewController activatableNotificationViewController,
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
MetricsLogger metricsLogger,
@@ -135,6 +136,7 @@
Optional<BubblesManager> bubblesManagerOptional,
ExpandableNotificationRowDragController dragController) {
mView = view;
+ mLockscreenUserManager = lockscreenUserManager;
mListContainer = listContainer;
mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
mActivatableNotificationViewController = activatableNotificationViewController;
@@ -214,6 +216,10 @@
mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
+ mLockscreenUserManager
+ .addOnNeedsRedactionInPublicChangedListener(mNeedsRedactionListener);
+ mNeedsRedactionListener.run();
+
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -232,6 +238,14 @@
});
}
+ private final Runnable mNeedsRedactionListener = new Runnable() {
+ @Override
+ public void run() {
+ mView.setSensitive(
+ mLockscreenUserManager.notifNeedsRedactionInPublic(mView.getEntry()));
+ }
+ };
+
private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -333,4 +347,5 @@
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
mView.setFeedbackIcon(icon);
}
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index ba26cfa..a7c6bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.policy.SmartReplyView;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.wmshell.BubblesManager;
import java.io.PrintWriter;
@@ -1994,22 +1995,33 @@
}
}
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pwOriginal, String[] args) {
+ IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
pw.print("contentView visibility: " + getVisibility());
pw.print(", alpha: " + getAlpha());
pw.print(", clipBounds: " + getClipBounds());
pw.print(", contentHeight: " + mContentHeight);
- pw.print(", visibleType: " + mVisibleType);
- View view = getViewForVisibleType(mVisibleType);
- pw.print(", visibleView ");
- if (view != null) {
- pw.print(" visibility: " + view.getVisibility());
- pw.print(", alpha: " + view.getAlpha());
- pw.print(", clipBounds: " + view.getClipBounds());
- } else {
- pw.print("null");
- }
- pw.println();
+ pw.println(", currentVisibleType: " + mVisibleType);
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ int[] visTypes = {
+ VISIBLE_TYPE_CONTRACTED,
+ VISIBLE_TYPE_EXPANDED,
+ VISIBLE_TYPE_HEADSUP,
+ VISIBLE_TYPE_SINGLELINE
+ };
+ for (int visType : visTypes) {
+ pw.print("visType: " + visType + " :: ");
+ View view = getViewForVisibleType(visType);
+ if (view != null) {
+ pw.print("visibility: " + view.getVisibility());
+ pw.print(", alpha: " + view.getAlpha());
+ pw.print(", clipBounds: " + view.getClipBounds());
+ } else {
+ pw.print("null");
+ }
+ pw.println();
+ }
+ });
}
/** Add any existing SmartReplyView to the dump */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index f26ecc3..a52f638 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -102,8 +102,9 @@
* @see InflationFlag
*/
public void markContentViewsFreeable(@InflationFlag int contentViews) {
+ @InflationFlag int existingContentViews = contentViews &= mContentViews;
mContentViews &= ~contentViews;
- mDirtyContentViews &= ~contentViews;
+ mDirtyContentViews |= existingContentViews;
}
public @InflationFlag int getContentViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3ea5e5b..24369e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5517,6 +5517,7 @@
*/
public void setFractionToShade(float fraction) {
mAmbientState.setFractionToShade(fraction);
+ updateContentHeight(); // Recompute stack height with different section gap.
requestChildrenUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2493ccb..6a8f479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -216,6 +216,9 @@
mBarState = mStatusBarStateController.getState();
mStatusBarStateController.addCallback(
mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
+ mLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener(
+ mOnNeedsRedactionInPublicChangedListener);
+ updateClearButtonVisibility();
}
@Override
@@ -223,6 +226,8 @@
mConfigurationController.removeCallback(mConfigurationListener);
mZenModeController.removeCallback(mZenModeControllerCallback);
mStatusBarStateController.removeCallback(mStateListener);
+ mLockscreenUserManager.removeOnNeedsRedactionInPublicChangedListener(
+ mOnNeedsRedactionInPublicChangedListener);
}
};
@@ -326,6 +331,7 @@
mLockscreenUserManager.isAnyProfilePublicMode());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
mNotificationEntryManager.updateNotifications("CentralSurfaces state changed");
+ updateClearButtonVisibility();
}
};
@@ -338,6 +344,17 @@
}
};
+ private final Runnable mOnNeedsRedactionInPublicChangedListener = new Runnable() {
+ @Override
+ public void run() {
+ // Whether or not the notification needs redaction when in public has changed, but if
+ // we're not actually in public, then we don't need to update anything.
+ if (mLockscreenUserManager.isAnyProfilePublicMode()) {
+ updateClearButtonVisibility();
+ }
+ }
+ };
+
/**
* Set the overexpansion of the panel to be applied to the view.
*/
@@ -1274,12 +1291,44 @@
return hasNotifications(selection, true /* clearable */);
}
+ private boolean hasRedactedClearableSilentNotifs() {
+ if (!mLockscreenUserManager.isAnyProfilePublicMode()) {
+ return false;
+ }
+ for (int userId : mNotifStats.getClearableSilentSensitiveNotifUsers()) {
+ if (mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(userId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasClearableSilentNotifs() {
+ return mNotifStats.getHasClearableSilentNotifs() && !hasRedactedClearableSilentNotifs();
+ }
+
+ private boolean hasRedactedClearableAlertingNotifs() {
+ if (!mLockscreenUserManager.isAnyProfilePublicMode()) {
+ return false;
+ }
+ for (int userId : mNotifStats.getClearableAlertingSensitiveNotifUsers()) {
+ if (mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(userId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasClearableAlertingNotifs() {
+ return mNotifStats.getHasClearableAlertingNotifs() && !hasRedactedClearableAlertingNotifs();
+ }
+
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
boolean hasAlertingMatchingClearable = isClearable
- ? mNotifStats.getHasClearableAlertingNotifs()
+ ? hasClearableAlertingNotifs()
: mNotifStats.getHasNonClearableAlertingNotifs();
boolean hasSilentMatchingClearable = isClearable
- ? mNotifStats.getHasClearableSilentNotifs()
+ ? hasClearableSilentNotifs()
: mNotifStats.getHasNonClearableSilentNotifs();
switch (selection) {
case ROWS_GENTLE:
@@ -1579,6 +1628,15 @@
mNotificationActivityStarter = activityStarter;
}
+ private void updateClearButtonVisibility() {
+ updateClearSilentButton();
+ updateFooter();
+ }
+
+ private void updateClearSilentButton() {
+ mSilentHeaderController.setClearSectionEnabled(hasClearableSilentNotifs());
+ }
+
/**
* Enum for UiEvent logged from this class
*/
@@ -1904,6 +1962,7 @@
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
+ updateClearSilentButton();
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2b79986..799fee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -49,9 +49,10 @@
public static final float START_FRACTION = 0.5f;
- private static final String LOG_TAG = "StackScrollAlgorithm";
- private final ViewGroup mHostView;
+ private static final String TAG = "StackScrollAlgorithm";
+ private static final Boolean DEBUG = false;
+ private final ViewGroup mHostView;
private int mPaddingBetweenElements;
private int mGapHeight;
private int mGapHeightOnLockscreen;
@@ -126,6 +127,37 @@
return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
}
+ private void log(String s) {
+ if (DEBUG) {
+ android.util.Log.i(TAG, s);
+ }
+ }
+
+ public void logView(View view, String s) {
+ String viewString = "";
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = ((ExpandableNotificationRow) view);
+ if (row.getEntry() == null) {
+ viewString = "ExpandableNotificationRow has null NotificationEntry";
+ } else {
+ viewString = row.getEntry().getSbn().getId() + "";
+ }
+ } else if (view == null) {
+ viewString = "View is null";
+ } else if (view instanceof SectionHeaderView) {
+ viewString = "SectionHeaderView";
+ } else if (view instanceof FooterView) {
+ viewString = "FooterView";
+ } else if (view instanceof MediaContainerView) {
+ viewString = "MediaContainerView";
+ } else if (view instanceof EmptyShadeView) {
+ viewString = "EmptyShadeView";
+ } else {
+ viewString = view.toString();
+ }
+ log(viewString + " " + s);
+ }
+
private void resetChildViewStates() {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 9863a0e..f386797 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -40,8 +41,8 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.ViewController;
-import java.util.Optional;
import java.util.ArrayList;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -61,17 +62,17 @@
private final NotificationIconAreaController mNotificationIconAreaController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationStackScrollLayoutController mStackScrollerController;
-
private final DarkIconDispatcher mDarkIconDispatcher;
private final NotificationPanelViewController mNotificationPanelViewController;
- private final Consumer<ExpandableNotificationRow>
- mSetTrackingHeadsUp = this::setTrackingHeadsUp;
+ private final Consumer<ExpandableNotificationRow> mSetTrackingHeadsUp =
+ this::setTrackingHeadsUp;
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
private final KeyguardBypassController mBypassController;
private final StatusBarStateController mStatusBarStateController;
private final CommandQueue mCommandQueue;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
-
+ private final NotificationLockscreenUserManager mNotifLockscreenUserManager;
+ private final Runnable mRedactionChanged = this::updateRedaction;
private final View mClockView;
private final Optional<View> mOperatorNameViewOptional;
@@ -90,6 +91,13 @@
};
private boolean mAnimationsEnabled = true;
private final KeyguardStateController mKeyguardStateController;
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStatePostChange() {
+ updateRedaction();
+ }
+ };
@VisibleForTesting
@Inject
@@ -98,6 +106,7 @@
HeadsUpManagerPhone headsUpManager,
StatusBarStateController stateController,
KeyguardBypassController bypassController,
+ NotificationLockscreenUserManager notifLockscreenUserManager,
NotificationWakeUpCoordinator wakeUpCoordinator,
DarkIconDispatcher darkIconDispatcher,
KeyguardStateController keyguardStateController,
@@ -125,6 +134,7 @@
mClockView = clockView;
mOperatorNameViewOptional = operatorNameViewOptional;
mDarkIconDispatcher = darkIconDispatcher;
+ mNotifLockscreenUserManager = notifLockscreenUserManager;
mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
@@ -156,6 +166,8 @@
mNotificationPanelViewController.setHeadsUpAppearanceController(this);
mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.addDarkReceiver(this);
+ mNotifLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener(mRedactionChanged);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
}
@Override
@@ -167,6 +179,9 @@
mNotificationPanelViewController.setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.removeDarkReceiver(this);
+ mNotifLockscreenUserManager
+ .removeOnNeedsRedactionInPublicChangedListener(mRedactionChanged);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
@@ -180,6 +195,19 @@
updateHeader(entry);
}
+ private void updateRedaction() {
+ NotificationEntry showingEntry = mView.getShowingEntry();
+ if (showingEntry == null) {
+ return;
+ }
+ int notifUserId = showingEntry.getSbn().getUserId();
+ boolean redactSensitiveContent =
+ mNotifLockscreenUserManager.isLockscreenPublicMode(notifUserId)
+ && mNotifLockscreenUserManager
+ .sensitiveNotifsNeedRedactionInPublic(notifUserId);
+ mView.setRedactSensitiveContent(redactSensitiveContent);
+ }
+
private void updateTopEntry() {
NotificationEntry newEntry = null;
if (shouldBeVisible()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 83970dc..629aa03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -220,7 +220,7 @@
}
}
- public float getMinStackScrollerPadding() {
+ public float getLockscreenMinStackScrollerPadding() {
if (mBypassEnabled) {
return mUnlockedStackScrollerPadding;
} else if (mIsSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 985df42..f15ea62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -24,6 +24,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -31,7 +32,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
@@ -325,6 +325,11 @@
private boolean mShouldUseSplitNotificationShade;
// The bottom padding reserved for elements of the keyguard measuring notifications
private float mKeyguardNotificationBottomPadding;
+ /**
+ * The top padding from where notification should start in lockscreen.
+ * Should be static also during animations and should match the Y of the first notification.
+ */
+ private float mKeyguardNotificationTopPadding;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -936,7 +941,7 @@
// the launcher icons animation starts, so use that as our
// duration.
.setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_DECELERATE)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
.withEndAction(() -> {
instantCollapse();
mView.setAlpha(1f);
@@ -1513,7 +1518,10 @@
*/
@VisibleForTesting
float getSpaceForLockscreenNotifications() {
- float topPadding = mNotificationStackScrollLayoutController.getTopPadding();
+ float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
+ // getMinStackScrollerPadding is from the top of the screen,
+ // but we need it from the top of the NSSL.
+ - mNotificationStackScrollLayoutController.getTop();
// Space between bottom of notifications and top of lock icon or udfps background.
float lockIconPadding = mLockIconViewController.getTop();
@@ -1525,11 +1533,15 @@
float bottomPadding = Math.max(lockIconPadding,
Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));
- mKeyguardNotificationBottomPadding = bottomPadding;
+ mKeyguardNotificationBottomPadding = bottomPadding;
+ mKeyguardNotificationTopPadding = staticTopPadding;
+
+ // To debug the available space, enable debug lines in this class. If you change how the
+ // available space is calculated, please also update those lines.
float availableSpace =
mNotificationStackScrollLayoutController.getHeight()
- - topPadding
+ - staticTopPadding
- bottomPadding;
return availableSpace;
}
@@ -1677,7 +1689,17 @@
mQsExpandImmediate = true;
setShowShelfOnly(true);
}
- if (isFullyCollapsed()) {
+ if (mShouldUseSplitNotificationShade && isOnKeyguard()) {
+ // It's a special case as this method is likely to not be initiated by finger movement
+ // but rather called from adb shell or accessibility service.
+ // We're using LockscreenShadeTransitionController because on lockscreen that's the
+ // source of truth for all shade motion. Not using it would make part of state to be
+ // outdated and will cause bugs. Ideally we'd use this controller also for non-split
+ // case but currently motion in portrait looks worse than when using flingSettings.
+ // TODO: make below function transitioning smoothly also in portrait with null target
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
+ } else if (isFullyCollapsed()) {
expand(true /* animate */);
} else {
traceQsJank(true /* startTracing */, false /* wasCancelled */);
@@ -2342,9 +2364,12 @@
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
setQSClippingBounds();
- // Only need to notify the notification stack when we're not in split screen mode. If we
- // do, then the notification panel starts scrolling along with the QS.
- if (!mShouldUseSplitNotificationShade) {
+ if (mShouldUseSplitNotificationShade) {
+ // In split shade we want to pretend that QS are always collapsed so their behaviour and
+ // interactions don't influence notifications as they do in portrait. But we want to set
+ // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1.
+ mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
+ } else {
mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
}
@@ -3197,6 +3222,8 @@
updateTrackingHeadsUp(null);
mExpandingFromHeadsUp = false;
setPanelScrimMinFraction(0.0f);
+ // Reset status bar alpha so alpha can be calculated upon updating view state.
+ setKeyguardStatusBarAlpha(-1f);
}
private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
@@ -3916,10 +3943,6 @@
}
}
- public boolean hasActiveClearableNotifications() {
- return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
- }
-
public RemoteInputController.Delegate createRemoteInputDelegate() {
return mNotificationStackScrollLayoutController.createDelegate();
}
@@ -4923,6 +4946,20 @@
drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
"mLockIconViewController.getTop()");
+ if (mKeyguardShowing) {
+ // Notifications have the space between those two lines.
+ drawDebugInfo(canvas,
+ mNotificationStackScrollLayoutController.getTop() +
+ (int) mKeyguardNotificationTopPadding,
+ Color.RED,
+ "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+ drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
+ (int) mKeyguardNotificationBottomPadding,
+ Color.RED,
+ "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+ }
+
mDebugPaint.setColor(Color.CYAN);
canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index fb8397b..7aeb08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -53,6 +53,8 @@
private View mCutoutSpace;
@Nullable
private DisplayCutout mDisplayCutout;
+ @Nullable
+ private Rect mDisplaySize;
private int mStatusBarHeight;
@Nullable
private TouchEventHandler mTouchEventHandler;
@@ -87,7 +89,7 @@
// Always have Battery meters in the status bar observe the dark/light modes.
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock);
- if (updateOrientationAndCutout()) {
+ if (updateDisplayParameters()) {
updateLayoutForCutout();
}
}
@@ -106,7 +108,7 @@
updateResources();
// May trigger cutout space layout-ing
- if (updateOrientationAndCutout()) {
+ if (updateDisplayParameters()) {
updateLayoutForCutout();
requestLayout();
}
@@ -114,7 +116,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (updateOrientationAndCutout()) {
+ if (updateDisplayParameters()) {
updateLayoutForCutout();
requestLayout();
}
@@ -124,7 +126,7 @@
/**
* @return boolean indicating if we need to update the cutout location / margins
*/
- private boolean updateOrientationAndCutout() {
+ private boolean updateDisplayParameters() {
boolean changed = false;
int newRotation = RotationUtils.getExactRotation(mContext);
if (newRotation != mRotationOrientation) {
@@ -137,6 +139,13 @@
mDisplayCutout = getRootWindowInsets().getDisplayCutout();
}
+ final Rect newSize = mContext.getResources().getConfiguration().windowConfiguration
+ .getMaxBounds();
+ if (!Objects.equals(newSize, mDisplaySize)) {
+ changed = true;
+ mDisplaySize = newSize;
+ }
+
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 67de4e3..f5462bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -38,6 +38,7 @@
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.android.systemui.util.leak.RotationUtils.getExactRotation
import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
+import com.android.systemui.util.traceSection
import java.io.PrintWriter
import java.lang.Math.max
@@ -133,8 +134,10 @@
* (i.e., ROTATION_NONE will always return the same bounds regardless of the context
* from which this method is called)
*/
- fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect {
- var insets = insetsCache[getCacheKey(rotation = rotation)]
+ fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int,
+ displayCutout: DisplayCutout?): Rect {
+ val key = getCacheKey(rotation, displayCutout)
+ var insets = insetsCache[key]
if (insets == null) {
insets = getStatusBarContentAreaForRotation(rotation)
}
@@ -156,20 +159,23 @@
*
* @param rotation the target rotation for which to calculate insets
*/
- fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> {
- val key = getCacheKey(rotation)
+ fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
+ traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
+ val displayCutout = context.display.cutout
+ val key = getCacheKey(rotation, displayCutout)
- val point = Point()
- context.display.getRealSize(point)
- // Target rotation can be a different orientation than the current device rotation
- point.orientToRotZero(getExactRotation(context))
- val width = point.logicalWidth(rotation)
+ val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
+ val point = Point(screenBounds.width(), screenBounds.height())
- val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, getResourcesForRotation(rotation, context), key)
+ // Target rotation can be a different orientation than the current device rotation
+ point.orientToRotZero(getExactRotation(context))
+ val width = point.logicalWidth(rotation)
- return Pair(area.left, width - area.right)
- }
+ val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
+ rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+
+ Pair(area.left, width - area.right)
+ }
/**
* Calculate the left and right insets for the status bar content in the device's current
@@ -192,9 +198,10 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val key = getCacheKey(rotation)
+ val displayCutout = context.display.cutout
+ val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, getResourcesForRotation(rotation, context), key)
+ rotation, displayCutout, getResourcesForRotation(rotation, context), key)
}
/**
@@ -207,20 +214,21 @@
private fun getAndSetCalculatedAreaForRotation(
@Rotation targetRotation: Int,
+ displayCutout: DisplayCutout?,
rotatedResources: Resources,
key: CacheKey
): Rect {
- return getCalculatedAreaForRotation(targetRotation, rotatedResources)
+ return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
.also {
insetsCache.put(key, it)
}
}
private fun getCalculatedAreaForRotation(
+ displayCutout: DisplayCutout?,
@Rotation targetRotation: Int,
rotatedResources: Resources
): Rect {
- val dc = context.display.cutout
val currentRotation = getExactRotation(context)
val roundedCornerPadding = rotatedResources
@@ -245,7 +253,7 @@
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ displayCutout,
context.resources.configuration.windowConfiguration.maxBounds,
SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
minLeft,
@@ -266,15 +274,19 @@
pw.println(insetsCache)
}
- private fun getCacheKey(@Rotation rotation: Int): CacheKey =
+ private fun getCacheKey(
+ @Rotation rotation: Int,
+ displayCutout: DisplayCutout?): CacheKey =
CacheKey(
- uniqueDisplayId = context.display.uniqueId,
- rotation = rotation
+ rotation = rotation,
+ displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds),
+ displayCutout = displayCutout
)
private data class CacheKey(
- val uniqueDisplayId: String,
- @Rotation val rotation: Int
+ @Rotation val rotation: Int,
+ val displaySize: Rect,
+ val displayCutout: DisplayCutout?
)
}
@@ -369,7 +381,7 @@
/**
* Calculate the insets needed from the left and right edges for the given rotation.
*
- * @param dc Device display cutout
+ * @param displayCutout Device display cutout
* @param sbHeight appropriate status bar height for this rotation
* @param width display width calculated for ROTATION_NONE
* @param height display height calculated for ROTATION_NONE
@@ -386,7 +398,7 @@
* rotation
*/
private fun getStatusBarLeftRight(
- dc: DisplayCutout?,
+ displayCutout: DisplayCutout?,
sbHeight: Int,
width: Int,
height: Int,
@@ -402,7 +414,7 @@
val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
- val cutoutRects = dc?.boundingRects
+ val cutoutRects = displayCutout?.boundingRects
if (cutoutRects == null || cutoutRects.isEmpty()) {
return Rect(minLeft,
0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index aa061d7..037063f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -411,8 +411,8 @@
if (nowExpanded) {
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
- } else if (clickedEntry.isSensitive()
- && mDynamicPrivacyController.isInLockedDownShade()) {
+ } else if (mDynamicPrivacyController.isInLockedDownShade()
+ && mLockscreenUserManager.notifNeedsRedactionInPublic(clickedEntry)) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
, null /* cancelRunnable */, false /* afterKeyguardGone */);
@@ -480,7 +480,7 @@
.isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
boolean userPublic = devicePublic
|| mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+ boolean needsRedaction = mLockscreenUserManager.notifNeedsRedactionInPublic(entry);
if (userPublic && needsRedaction) {
// TODO(b/135046837): we can probably relax this with dynamic privacy
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index c1d0769..b117515 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -311,9 +311,9 @@
// We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
// already expanded and showing notifications/QS, the animation looks really messy. For now,
- // disable it if the notification panel is not fully collapsed.
+ // disable it if the notification panel is expanded.
if ((!this::mCentralSurfaces.isInitialized ||
- !mCentralSurfaces.notificationPanelViewController.isFullyCollapsed) &&
+ mCentralSurfaces.notificationPanelViewController.isExpanded) &&
// Status bar might be expanded because we have started
// playing the animation already
!isAnimationPlaying()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 15ee553..dce2412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -18,6 +18,7 @@
import android.app.IActivityTaskManager;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
@@ -94,6 +95,15 @@
boolean isKeyguardGoingAway();
/**
+ * Whether we're currently animating between the keyguard and the app/launcher surface behind
+ * it, or will be shortly (which happens if we started a fling to dismiss the keyguard).
+ * @see {@link KeyguardViewMediator#isAnimatingBetweenKeyguardAndSurfaceBehind()}
+ */
+ default boolean isAnimatingBetweenKeyguardAndSurfaceBehind() {
+ return false;
+ };
+
+ /**
* @return a shortened fading away duration similar to
* {{@link #getKeyguardFadingAwayDuration()}} which may only span half of the duration, unless
* we're bypassing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 77e285d..2a225b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -279,6 +279,11 @@
}
@Override
+ public boolean isAnimatingBetweenKeyguardAndSurfaceBehind() {
+ return mUnlockAnimationControllerLazy.get().isAnimatingBetweenKeyguardAndSurfaceBehind();
+ }
+
+ @Override
public boolean isBypassFadingAnimation() {
return mBypassFadingAnimation;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index c53d510..e0d780a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -31,7 +31,9 @@
import android.graphics.Rect;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Log;
+import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.IWindowManager;
import android.view.Surface;
@@ -130,7 +132,9 @@
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
+ Trace.beginSection("StatusBarWindowController.getBarLayoutParams");
mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
+ Trace.endSection();
mWindowManager.addView(mStatusBarWindowView, mLp);
mLpChanged.copyFrom(mLp);
@@ -223,14 +227,15 @@
private void calculateStatusBarLocationsForAllRotations() {
Rect[] bounds = new Rect[4];
+ final DisplayCutout displayCutout = mContext.getDisplay().getCutout();
bounds[0] = mContentInsetsProvider
- .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE);
+ .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE, displayCutout);
bounds[1] = mContentInsetsProvider
- .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE);
+ .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE, displayCutout);
bounds[2] = mContentInsetsProvider
- .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN);
+ .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN, displayCutout);
bounds[3] = mContentInsetsProvider
- .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE);
+ .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE, displayCutout);
try {
mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java b/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java
index 2b7a332..90f2434 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java
@@ -18,15 +18,18 @@
import android.app.Activity;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
+import android.view.View;
import android.view.WindowManager;
import com.android.systemui.R;
+import java.util.Collections;
import java.util.function.Consumer;
/**
@@ -75,6 +78,12 @@
getWindow().setElevation(getWindow().getElevation() + 5);
getWindow().setBackgroundBlurRadius(getResources().getDimensionPixelSize(
R.dimen.bottom_sheet_background_blur_radius));
+
+ final View rootView = findViewById(R.id.bottom_sheet);
+ rootView.addOnLayoutChangeListener((view, l, t, r, b, oldL, oldT, oldR, oldB) -> {
+ rootView.setUnrestrictedPreferKeepClearRects(
+ Collections.singletonList(new Rect(0, 0, r - l, b - t)));
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index b7f90a4..4685c14 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -38,7 +38,6 @@
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -102,9 +101,6 @@
}
@Binds
- abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
-
- @Binds
abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 3329eab..ad73491 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -71,6 +71,11 @@
private var popupMenu: UserSwitcherPopupMenu? = null
private lateinit var addButton: View
private var addUserRecords = mutableListOf<UserRecord>()
+ private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ finish()
+ }
+ }
// When the add users options become available, insert another option to manage users
private val manageUserRecord = UserRecord(
null /* info */,
@@ -215,11 +220,7 @@
initBroadcastReceiver()
parent.post { buildUserViews() }
- userTracker.addCallback(object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- finish()
- }
- }, mainExecutor)
+ userTracker.addCallback(userSwitchedCallback, mainExecutor)
}
private fun showPopupMenu() {
@@ -340,6 +341,7 @@
super.onDestroy()
broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+ userTracker.removeCallback(userSwitchedCallback)
}
private fun initBroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5b5dca3..d54de3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,9 +21,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.provider.Settings;
-import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import com.android.internal.policy.SystemBarUtils;
@@ -35,6 +33,8 @@
public class Utils {
+ private static Boolean sUseQsMediaPlayer = null;
+
/**
* Allows lambda iteration over a list. It is done in reverse order so it is safe
* to add or remove items during the iteration. Skips over null items.
@@ -81,9 +81,16 @@
* Off by default, but can be disabled by setting to 0
*/
public static boolean useQsMediaPlayer(Context context) {
- int flag = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
- return flag > 0;
+ // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value
+ // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply
+ // cache the first result we fetch and use that going forward. Do this to avoid unnecessary
+ // binder calls which may happen on the critical path.
+ if (sUseQsMediaPlayer == null) {
+ int flag = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
+ sUseQsMediaPlayer = flag > 0;
+ }
+ return sUseQsMediaPlayer;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index cddfd44..a6db2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -200,9 +200,13 @@
mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
- pip.hidePipMenu(null, null);
- }
+ pip.onKeyguardVisibilityChanged(showing,
+ mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ pip.onKeyguardDismissAnimationFinished();
}
};
mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 7476490..39cc34b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -18,6 +18,7 @@
import android.content.ContentResolver
import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
@@ -44,18 +45,20 @@
private val fakeWakeUri = Uri.Builder().appendPath("wake").build()
private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build()
private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build()
+ private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
+ private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
+ private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
@Mock
private lateinit var secureSettings: SecureSettings
-
@Mock
private lateinit var contentResolver: ContentResolver
-
@Mock
private lateinit var handler: Handler
-
@Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@@ -72,6 +75,13 @@
.thenReturn(fakeUnlockIntentUri)
`when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
.thenReturn(fakeBioFailUri)
+ `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS))
+ .thenReturn(fakeFaceErrorsUri)
+ `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
+ .thenReturn(fakeFaceAcquiredUri)
+ `when`(secureSettings.getUriFor(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
+ .thenReturn(fakeUnlockIntentBioEnroll)
activeUnlockConfig = ActiveUnlockConfig(
handler,
@@ -99,12 +109,7 @@
// WHEN unlock on wake is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeWakeUri),
- 0,
- 0
- )
+ updateSetting(fakeWakeUri)
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
@@ -134,12 +139,7 @@
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeUnlockIntentUri),
- 0,
- 0
- )
+ updateSetting(fakeUnlockIntentUri)
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -154,19 +154,19 @@
fun testOnBioFailSettingChanged() {
verifyRegisterSettingObserver()
- // GIVEN no active unlock settings enabled
+ // GIVEN no active unlock settings enabled and triggering unlock intent on biometric
+ // enrollment setting is disabled (empty string is disabled, null would use the default)
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn("")
+ updateSetting(fakeUnlockIntentBioEnroll)
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeBioFailUri),
- 0,
- 0
- )
+ updateSetting(fakeBioFailUri)
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -177,21 +177,146 @@
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
}
+ @Test
+ fun testFaceErrorSettingsChanged() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // WHEN face error timeout (3), allow trigger active unlock
+ `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ 0)).thenReturn("3")
+ updateSetting(fakeFaceAcquiredUri)
+
+ // THEN active unlock triggers allowed on error TIMEOUT
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_CANCELED))
+ }
+
+ @Test
+ fun testFaceAcquiredSettingsChanged() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
+ `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ 0)).thenReturn(
+ "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+ "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}")
+ updateSetting(fakeFaceAcquiredUri)
+
+ // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_GOOD))
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+ }
+
+ @Test
+ fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // GIVEN fingerprint and face are NOT enrolled
+ activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+ // WHEN unlock intent is allowed when NO biometrics are enrolled (0)
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}")
+ updateSetting(fakeUnlockIntentBioEnroll)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ }
+
+ @Test
+ fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // GIVEN fingerprint and face are both enrolled
+ activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+ // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
+ // are enrolled
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn(
+ "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" +
+ "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}")
+ updateSetting(fakeUnlockIntentBioEnroll)
+
+ // THEN active unlock triggers NOT allowed on unlock intent
+ assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+ // WHEN fingerprint ONLY enrolled
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+ // WHEN face ONLY enrolled
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ }
+
+ private fun updateSetting(uri: Uri) {
+ settingsObserverCaptor.value.onChange(
+ false,
+ listOf(uri),
+ 0,
+ 0 /* flags */
+ )
+ }
+
private fun verifyRegisterSettingObserver() {
- verify(contentResolver).registerContentObserver(
- eq(fakeWakeUri),
- eq(false),
- capture(settingsObserverCaptor),
- eq(UserHandle.USER_ALL))
+ verifyRegisterSettingObserver(fakeWakeUri)
+ verifyRegisterSettingObserver(fakeUnlockIntentUri)
+ verifyRegisterSettingObserver(fakeBioFailUri)
+ verifyRegisterSettingObserver(fakeFaceErrorsUri)
+ verifyRegisterSettingObserver(fakeFaceAcquiredUri)
+ verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
+ }
+ private fun verifyRegisterSettingObserver(uri: Uri) {
verify(contentResolver).registerContentObserver(
- eq(fakeUnlockIntentUri),
- eq(false),
- capture(settingsObserverCaptor),
- eq(UserHandle.USER_ALL))
-
- verify(contentResolver).registerContentObserver(
- eq(fakeBioFailUri),
+ eq(uri),
eq(false),
capture(settingsObserverCaptor),
eq(UserHandle.USER_ALL))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 7c211b2..57253af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -159,6 +159,7 @@
when(mContext.getDisplay()).thenReturn(mDisplay);
// Not support hwc layer by default
doReturn(null).when(mDisplay).getDisplayDecorationSupport();
+ doReturn(mDisplayMode).when(mDisplay).getMode();
when(mMockTypedArray.length()).thenReturn(0);
mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..a52c4a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.systemui.biometrics
+
+import android.app.Instrumentation
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import com.android.systemui.util.mockito.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var dumpManager: DumpManager
+ @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock lateinit var broadcastSender: BroadcastSender
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var panelExpansionStateManager: PanelExpansionStateManager
+
+ private lateinit var instrumentation: Instrumentation
+ private lateinit var uiEventLogger: UiEventLoggerFake
+ private lateinit var udfpsBpView: UdfpsBpView
+ private lateinit var statusBarStateController: StatusBarStateControllerImpl
+ private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+ @Before
+ fun setup() {
+ instrumentation = getInstrumentation()
+ instrumentation.runOnMainSync { createUdfpsView() }
+ instrumentation.waitForIdleSync()
+
+ uiEventLogger = UiEventLoggerFake()
+ statusBarStateController =
+ StatusBarStateControllerImpl(uiEventLogger, dumpManager, interactionJankMonitor)
+ udfpsBpViewController = UdfpsBpViewController(
+ udfpsBpView,
+ statusBarStateController,
+ panelExpansionStateManager,
+ systemUIDialogManager,
+ broadcastSender,
+ dumpManager)
+ udfpsBpViewController.init()
+ }
+
+ @After
+ fun tearDown() {
+ if (udfpsBpViewController.isAttachedToWindow) {
+ instrumentation.runOnMainSync { ViewUtils.detachView(udfpsBpView) }
+ instrumentation.waitForIdleSync()
+ }
+ }
+
+ private fun createUdfpsView() {
+ context.setTheme(R.style.Theme_AppCompat)
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
+ udfpsBpView = UdfpsBpView(context, null)
+ }
+
+ @Test
+ fun addExpansionListener() {
+ instrumentation.runOnMainSync { ViewUtils.attachView(udfpsBpView) }
+ instrumentation.waitForIdleSync()
+
+ // Both UdfpsBpViewController & UdfpsAnimationViewController add listener
+ verify(panelExpansionStateManager, times(2)).addExpansionListener(any())
+ }
+
+ @Test
+ fun removeExpansionListener() {
+ instrumentation.runOnMainSync { ViewUtils.attachView(udfpsBpView) }
+ instrumentation.waitForIdleSync()
+ instrumentation.runOnMainSync { ViewUtils.detachView(udfpsBpView) }
+ instrumentation.waitForIdleSync()
+
+ // Both UdfpsBpViewController & UdfpsAnimationViewController remove listener
+ verify(panelExpansionStateManager, times(2)).removeExpansionListener(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index fc5ccbc..6eba215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -102,6 +103,7 @@
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var broadcastSender: BroadcastSender
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -131,7 +133,8 @@
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
- REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
+ REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator,
+ broadcastSender)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 87d3cd0..290965c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -66,6 +66,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
@@ -186,6 +187,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Mock
+ private BroadcastSender mBroadcastSender;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -261,7 +264,8 @@
mSystemUIDialogManager,
mLatencyTracker,
mActivityLaunchAnimator,
- Optional.of(mAlternateTouchProvider));
+ Optional.of(mAlternateTouchProvider),
+ mBroadcastSender);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index b87a7e7..0fdd905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -168,7 +168,8 @@
mController.onViewAttached();
verify(mView, atLeast(1)).setPauseAuth(true);
- verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount, true);
+ verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
}
@Test
@@ -195,7 +196,8 @@
final float eased = .65f;
mStatusBarStateListener.onDozeAmountChanged(linear, eased);
- verify(mView).onDozeAmountChanged(linear, eased, true);
+ verify(mView).onDozeAmountChanged(linear, eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index de04d3e..91214a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -19,6 +19,8 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -26,7 +28,9 @@
import static org.mockito.Mockito.when;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ClipboardManager;
+import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import androidx.test.filters.SmallTest;
@@ -139,4 +143,30 @@
verify(mClipboardOverlayControllerFactory, times(2)).create(any());
}
+
+ @Test
+ public void test_shouldSuppressOverlay() {
+ // Regardless of the package or emulator, nothing should be suppressed without the flag
+ assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource,
+ false));
+ assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData,
+ ClipboardListener.SHELL_PACKAGE, false));
+ assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource,
+ true));
+
+ ClipDescription desc = new ClipDescription("Test", new String[]{"text/plain"});
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(ClipboardListener.EXTRA_SUPPRESS_OVERLAY, true);
+ desc.setExtras(bundle);
+ ClipData suppressableClipData = new ClipData(desc, new ClipData.Item("Test Item"));
+
+ // Clip data with the suppression extra is only honored in the emulator or with the shell
+ // package.
+ assertFalse(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource,
+ false));
+ assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource,
+ true));
+ assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData,
+ ClipboardListener.SHELL_PACKAGE, false));
+ }
}
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 ae387e8..4eeb4ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,7 +45,6 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -91,8 +90,6 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private DevicePostureController mDevicePostureController;
- @Mock
- private BatteryController mBatteryController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -125,7 +122,7 @@
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController,
- mDevicePostureController, mBatteryController);
+ mDevicePostureController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -233,9 +230,7 @@
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
// WHEN the pick up gesture is triggered and keyguard isn't occluded
- // and device isn't on a wireless charger
when(mKeyguardStateController.isOccluded()).thenReturn(false);
- when(mBatteryController.isPluggedInWireless()).thenReturn(false);
mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
// THEN wakeup
@@ -249,22 +244,6 @@
// WHEN the pick up gesture is triggered and keyguard IS occluded
when(mKeyguardStateController.isOccluded()).thenReturn(true);
- when(mBatteryController.isPluggedInWireless()).thenReturn(false);
- mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
-
- // THEN never wakeup
- verify(mMachine, never()).wakeUp();
- }
-
- @Test
- public void testPickupGestureWirelessCharger() {
- // GIVEN device is in doze (screen blank, but running doze sensors)
- when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
-
- // WHEN the pick up gesture is triggered
- // and device IS on a wireless charger
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
- when(mBatteryController.isPluggedInWireless()).thenReturn(true);
mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
// THEN never wakeup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 5ff316e..c532ed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -34,8 +34,6 @@
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
@@ -77,9 +75,6 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-import java.util.ArrayList;
-import java.util.List;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -182,7 +177,7 @@
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
- Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+ Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
// WHEN lock icon view controller is initialized and attached
mLockIconViewController.init();
@@ -197,7 +192,7 @@
@Test
public void testUpdatePaddingBasedOnResolutionScale() {
// GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
- Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+ Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
when(mAuthController.getScaleFactor()).thenReturn(5f);
// WHEN lock icon view controller is initialized and attached
@@ -215,20 +210,19 @@
// GIVEN fp sensor location is not available pre-init
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- when(mAuthController.getUdfpsProps()).thenReturn(null);
mLockIconViewController.init();
captureAttachListener();
mAttachListener.onViewAttachedToWindow(mLockIconView);
- // GIVEN fp sensor location is available post-atttached
+ // GIVEN fp sensor location is available post-attached
captureAuthControllerCallback();
- Pair<Integer, PointF> udfps = setupUdfps();
+ Pair<Float, PointF> udfps = setupUdfps();
// WHEN all authenticators are registered
mAuthControllerCallback.onAllAuthenticatorsRegistered();
mDelayableExecutor.runAllReady();
- // THEN lock icon view location is updated with the same coordinates as fpProps
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
eq(PADDING));
}
@@ -402,21 +396,10 @@
verify(mLockIconView).setTranslationX(0);
}
- private Pair<Integer, PointF> setupUdfps() {
+ private Pair<Float, PointF> setupUdfps() {
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
final PointF udfpsLocation = new PointF(50, 75);
- final int radius = 33;
- final FingerprintSensorPropertiesInternal fpProps =
- new FingerprintSensorPropertiesInternal(
- /* sensorId */ 0,
- /* strength */ 0,
- /* max enrollments per user */ 5,
- /* component info */ new ArrayList<>(),
- /* sensorType */ 3,
- /* halControlsIllumination */ true,
- /* resetLockoutRequiresHwToken */ false,
- List.of(new SensorLocationInternal("" /* displayId */,
- (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
+ final float radius = 33f;
when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsRadius()).thenReturn(radius);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index f9fb865..241ed24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -59,6 +59,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -102,6 +103,7 @@
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
+private const val REC_APP_NAME = "REC APP NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -262,6 +264,7 @@
// Set valid recommendation data
val extras = Bundle()
+ extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
val intent = Intent().apply {
putExtras(extras)
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -1121,6 +1124,91 @@
verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
}
+ @Test
+ fun player_gutsOpen_contentDescriptionIsForGuts() {
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ player.attachPlayer(viewHolder)
+
+ val gutsTextString = "gutsText"
+ whenever(gutsText.text).thenReturn(gutsTextString)
+ player.bindPlayer(mediaData, KEY)
+
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).isEqualTo(gutsTextString)
+ }
+
+ @Test
+ fun player_gutsClosed_contentDescriptionIsForPlayer() {
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ player.attachPlayer(viewHolder)
+
+ val app = "appName"
+ player.bindPlayer(mediaData.copy(app = app), KEY)
+
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).contains(mediaData.song!!)
+ assertThat(description).contains(mediaData.artist!!)
+ assertThat(description).contains(app)
+ }
+
+ @Test
+ fun player_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
+ // Start out open
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ whenever(gutsText.text).thenReturn("gutsText")
+ player.attachPlayer(viewHolder)
+ val app = "appName"
+ player.bindPlayer(mediaData.copy(app = app), KEY)
+
+ // Update to closed by long pressing
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+ reset(viewHolder.player)
+
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ captor.value.onLongClick(viewHolder.player)
+
+ // Then content description is now the player content description
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).contains(mediaData.song!!)
+ assertThat(description).contains(mediaData.artist!!)
+ assertThat(description).contains(app)
+ }
+
+ @Test
+ fun player_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
+ // Start out closed
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ val gutsTextString = "gutsText"
+ whenever(gutsText.text).thenReturn(gutsTextString)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData.copy(app = "appName"), KEY)
+
+ // Update to open by long pressing
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+ reset(viewHolder.player)
+
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ captor.value.onLongClick(viewHolder.player)
+
+ // Then content description is now the guts content description
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).isEqualTo(gutsTextString)
+ }
+
/* ***** END guts tests for the player ***** */
/* ***** Guts tests for the recommendations ***** */
@@ -1189,6 +1277,85 @@
verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong())
}
+ @Test
+ fun recommendation_gutsOpen_contentDescriptionIsForGuts() {
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ player.attachRecommendation(recommendationViewHolder)
+
+ val gutsTextString = "gutsText"
+ whenever(gutsText.text).thenReturn(gutsTextString)
+ player.bindRecommendation(smartspaceData)
+
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).isEqualTo(gutsTextString)
+ }
+
+ @Test
+ fun recommendation_gutsClosed_contentDescriptionIsForPlayer() {
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ player.attachRecommendation(recommendationViewHolder)
+
+ player.bindRecommendation(smartspaceData)
+
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).contains(REC_APP_NAME)
+ }
+
+ @Test
+ fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
+ // Start out open
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ whenever(gutsText.text).thenReturn("gutsText")
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(smartspaceData)
+
+ // Update to closed by long pressing
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+ reset(viewHolder.player)
+
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ captor.value.onLongClick(viewHolder.player)
+
+ // Then content description is now the player content description
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).contains(REC_APP_NAME)
+ }
+
+ @Test
+ fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
+ // Start out closed
+ whenever(mediaViewController.isGutsVisible).thenReturn(false)
+ val gutsTextString = "gutsText"
+ whenever(gutsText.text).thenReturn(gutsTextString)
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(smartspaceData)
+
+ // Update to open by long pressing
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+ reset(viewHolder.player)
+
+ whenever(mediaViewController.isGutsVisible).thenReturn(true)
+ captor.value.onLongClick(viewHolder.player)
+
+ // Then content description is now the guts content description
+ val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+ verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+ val description = descriptionCaptor.value.toString()
+
+ assertThat(description).isEqualTo(gutsTextString)
+ }
+
/* ***** END guts tests for the recommendations ***** */
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 10eeb11..18ee791 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -25,19 +25,17 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
-
import com.google.common.truth.Truth.assertThat
-
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -50,8 +48,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
@@ -81,6 +79,7 @@
@Mock private lateinit var route: RoutingSessionInfo
@Mock private lateinit var controller: MediaController
@Mock private lateinit var playbackInfo: PlaybackInfo
+ @Mock private lateinit var configurationController: ConfigurationController
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -94,6 +93,7 @@
lmmFactory,
mr2,
muteAwaitFactory,
+ configurationController,
fakeFgExecutor,
fakeBgExecutor,
dumpster
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
index 604e1f3..1817809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
@@ -4,16 +4,22 @@
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.animation.WidgetState
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/**
* Tests for {@link MediaViewController}.
@@ -31,6 +37,9 @@
private lateinit var mediaViewController: MediaViewController
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
private var transitionLayout = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ @Mock private lateinit var mockViewState: TransitionViewState
+ @Mock private lateinit var mockCopiedState: TransitionViewState
+ @Mock private lateinit var mockWidgetState: WidgetState
@Before
fun setUp() {
@@ -63,4 +72,15 @@
mediaHostStateHolder.squishFraction = 0.5f
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
}
+
+ @Test
+ fun testSquish_DoesNotMutateViewState() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(mutableMapOf(R.id.album_art to mockWidgetState))
+
+ mediaViewController.squishViewState(mockViewState, 0.5f)
+ verify(mockViewState, times(1)).copy()
+ verifyNoMoreInteractions(mockViewState)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
index a074475..dafaa6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -35,6 +35,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@@ -73,6 +74,7 @@
@Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback>
@Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback>
+ @Captor lateinit var mediaControllerCallback: ArgumentCaptor<MediaController.Callback>
@Before
fun setUp() {
@@ -82,6 +84,7 @@
.thenReturn(browser)
whenever(mediaController.transportControls).thenReturn(transportControls)
+ whenever(mediaController.sessionToken).thenReturn(token)
resumeBrowser = TestableResumeMediaBrowser(
context,
@@ -137,6 +140,22 @@
}
@Test
+ fun testConnection_thenSessionDestroyed_disconnects() {
+ // When testConnection is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.testConnection()
+ verify(mediaController).registerCallback(mediaControllerCallback.capture())
+ reset(browser)
+
+ // And a sessionDestroyed event is triggered
+ mediaControllerCallback.value.onSessionDestroyed()
+
+ // Then we disconnect the browser and unregister the callback
+ verify(browser).disconnect()
+ verify(mediaController).unregisterCallback(mediaControllerCallback.value)
+ }
+
+ @Test
fun testConnection_calledTwice_oldBrowserDisconnected() {
val oldBrowser = mock<MediaBrowser>()
whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
@@ -188,6 +207,22 @@
}
@Test
+ fun testFindRecentMedia_thenSessionDestroyed_disconnects() {
+ // When findRecentMedia is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.findRecentMedia()
+ verify(mediaController).registerCallback(mediaControllerCallback.capture())
+ reset(browser)
+
+ // And a sessionDestroyed event is triggered
+ mediaControllerCallback.value.onSessionDestroyed()
+
+ // Then we disconnect the browser and unregister the callback
+ verify(browser).disconnect()
+ verify(mediaController).unregisterCallback(mediaControllerCallback.value)
+ }
+
+ @Test
fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() {
val oldBrowser = mock<MediaBrowser>()
whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
@@ -261,6 +296,22 @@
}
@Test
+ fun testRestart_thenSessionDestroyed_disconnects() {
+ // When restart is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.restart()
+ verify(mediaController).registerCallback(mediaControllerCallback.capture())
+ reset(browser)
+
+ // And a sessionDestroyed event is triggered
+ mediaControllerCallback.value.onSessionDestroyed()
+
+ // Then we disconnect the browser and unregister the callback
+ verify(browser).disconnect()
+ verify(mediaController).unregisterCallback(mediaControllerCallback.value)
+ }
+
+ @Test
fun testRestart_calledTwice_oldBrowserDisconnected() {
val oldBrowser = mock<MediaBrowser>()
whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 7c30398..1cfa3b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -47,6 +47,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.SystemClock;
@@ -60,6 +61,7 @@
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -101,6 +103,8 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -125,10 +129,10 @@
private SysuiTestableContext mSysuiTestableContextExternal;
@Mock
- NavigationBarFrame mNavigationBarFrame;
- @Mock
NavigationBarView mNavigationBarView;
@Mock
+ NavigationBarFrame mNavigationBarFrame;
+ @Mock
ButtonDispatcher mHomeButton;
@Mock
ButtonDispatcher mRecentsButton;
@@ -164,7 +168,7 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
- EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+ private ViewTreeObserver mViewTreeObserver;
@Mock
EdgeBackGestureHandler mEdgeBackGestureHandler;
NavBarHelper mNavBarHelper;
@@ -190,6 +194,9 @@
private CentralSurfaces mCentralSurfaces;
@Mock
private UserContextProvider mUserContextProvider;
+ @Mock
+ private Resources mResources;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@Rule
@@ -199,8 +206,6 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
- .thenReturn(mEdgeBackGestureHandler);
when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
@@ -213,8 +218,10 @@
when(mNavigationBarTransitions.getLightTransitionsController())
.thenReturn(mLightBarTransitionsController);
when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+ when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
+ when(mNavigationBarView.getResources()).thenReturn(mResources);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
@@ -222,8 +229,6 @@
mDependency.injectMockDependency(KeyguardStateController.class);
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectMockDependency(NavigationBarController.class);
- mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
- mEdgeBackGestureHandlerFactory);
mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
TestableLooper.get(this).runWithLooper(() -> {
@@ -452,6 +457,8 @@
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
mHandler,
+ mFakeExecutor,
+ mFakeExecutor,
mUiEventLogger,
mNavBarHelper,
mLightBarController,
@@ -463,6 +470,7 @@
mDeadZone,
mDeviceConfigProxyFake,
mNavigationBarTransitions,
+ mEdgeBackGestureHandler,
Optional.of(mock(BackAnimation.class)),
mUserContextProvider));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 4a6bbbc..346d1e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -24,7 +24,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,6 +31,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -41,7 +41,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DeviceConfigProxyFake;
@@ -90,8 +89,9 @@
when(mPackageManager.queryIntentActivities(any(Intent.class),
any(Integer.class))).thenReturn(resolveInfoList);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true);
- mContext.getOrCreateTestableResources().addOverride(R.string.def_qr_code_component,
- defaultActivity);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultQrCodeComponent, defaultActivity);
+
mContext.getOrCreateTestableResources().addOverride(
android.R.bool.config_enableQrCodeScannerOnLockScreen, enableOnLockScreen);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 562c970..73e574e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -130,8 +130,8 @@
whenever(statusbarStateController.state).thenReturn(StatusBarState.KEYGUARD)
whenever(nsslController.isInLockedDownShade).thenReturn(false)
whenever(qS.isFullyCollapsed).thenReturn(true)
- whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn(
- true)
+ whenever(lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt()))
+ .thenReturn(false)
whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true)
whenever(lockScreenUserManager.isLockscreenPublicMode(anyInt())).thenReturn(true)
whenever(falsingCollector.shouldEnforceBouncer()).thenReturn(false)
@@ -207,8 +207,8 @@
@Test
fun testTriggeringBouncerWhenPrivateNotificationsArentAllowed() {
- whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn(
- false)
+ whenever(lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt()))
+ .thenReturn(true)
transitionController.goToLockedShade(null)
verify(statusbarStateController, never()).setState(anyInt())
verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 7687d12..18937e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -53,11 +53,13 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -106,6 +108,10 @@
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private OverviewProxyService mOverviewProxyService;
private UserInfo mCurrentUser;
private UserInfo mSecondaryUser;
@@ -119,7 +125,6 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
int currentUserId = ActivityManager.getCurrentUser();
mSettings = new FakeSettings();
@@ -212,7 +217,7 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN current user's notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif));
}
@Test
@@ -223,7 +228,7 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN current user's notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif));
}
@Test
@@ -234,7 +239,7 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN work profile notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif));
}
@Test
@@ -245,7 +250,7 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN work profile notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif));
}
@Test
@@ -260,11 +265,11 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN the work profile notification doesn't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif));
// THEN the current user and secondary user notifications do need to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif));
}
@Test
@@ -279,11 +284,11 @@
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// THEN the work profile notification needs to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif));
// THEN the current user and secondary user notifications don't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif));
+ assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif));
}
@Test
@@ -298,7 +303,7 @@
// THEN the secondary profile notification still needs to be redacted because the current
// user's setting takes precedence
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif));
}
@Test
@@ -418,9 +423,12 @@
context,
mBroadcastDispatcher,
mDevicePolicyManager,
+ mKeyguardUpdateMonitor,
+ () -> mEntryManager,
+ () -> mOverviewProxyService,
mUserManager,
- (() -> mVisibilityProvider),
- (() -> mNotifCollection),
+ () -> mVisibilityProvider,
+ () -> mNotifCollection,
mClickNotifier,
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index 7d06abf..e43ae0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -124,8 +124,8 @@
}
private void allowPrivateNotificationsInPublic(boolean allow) {
- when(mLockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn(
- allow);
+ when(mLockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt())).thenReturn(
+ !allow);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 0fff5f5..16b0376 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -294,7 +294,8 @@
verify(mPresenter).updateNotificationViews(any());
verify(mEntryListener).onEntryRemoved(
- eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
+ argThat(matchEntryOnKey()), any(),
+ eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
verify(mRow).setRemoved();
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
@@ -319,8 +320,8 @@
mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(mEntryListener, never()).onEntryRemoved(
- eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
+ verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(),
+ eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
}
/** Regression test for b/201097913. */
@@ -333,10 +334,10 @@
// Verify that only the listener for the NEW pipeline is notified.
// Old pipeline:
verify(mEntryListener, never()).onEntryRemoved(
- argThat(matchEntryOnSbn()), any(), anyBoolean(), anyInt());
+ argThat(matchEntryOnKey()), any(), anyBoolean(), anyInt());
// New pipeline:
verify(mNotifCollectionListener).onEntryRemoved(
- argThat(matchEntryOnSbn()), anyInt());
+ argThat(matchEntryOnKey()), anyInt());
}
@Test
@@ -457,7 +458,7 @@
// THEN the notification is retained
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener, never()).onEntryRemoved(
- eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
+ argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
}
@Test
@@ -476,7 +477,7 @@
// THEN the notification is removed
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener).onEntryRemoved(
- eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
+ argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
}
@Test
@@ -541,7 +542,7 @@
// GIVEN interceptor that intercepts that entry
when(mRemoveInterceptor.onNotificationRemoveRequested(
- eq(mEntry.getKey()), eq(mEntry), anyInt()))
+ eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
.thenReturn(true);
// WHEN the notification is removed
@@ -549,7 +550,7 @@
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener, never()).onEntryRemoved(eq(mEntry),
+ verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()),
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
@@ -560,7 +561,7 @@
// GIVEN interceptor that doesn't intercept
when(mRemoveInterceptor.onNotificationRemoveRequested(
- eq(mEntry.getKey()), eq(mEntry), anyInt()))
+ eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
.thenReturn(false);
// WHEN the notification is removed
@@ -568,7 +569,7 @@
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener, atLeastOnce()).onEntryRemoved(eq(mEntry),
+ verify(mEntryListener, atLeastOnce()).onEntryRemoved(argThat(matchEntryOnKey()),
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
@@ -663,9 +664,8 @@
PendingIntent.FLAG_IMMUTABLE)).build();
}
- // TODO(b/201321631): Update more tests to use this function instead of eq(mEntry).
- private ArgumentMatcher<NotificationEntry> matchEntryOnSbn() {
- return e -> e.getSbn().equals(mSbn);
+ private ArgumentMatcher<NotificationEntry> matchEntryOnKey() {
+ return e -> e.getKey().equals(mEntry.getKey());
}
private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 540d291..1cce3b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -21,10 +21,13 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
@@ -32,28 +35,33 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
@SmallTest
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
var manager: NotificationSectionsFeatureManager? = null
val proxyFake = DeviceConfigProxyFake()
- var originalQsMediaPlayer: Int = 0
+ private lateinit var staticMockSession: MockitoSession
@Before
public fun setup() {
manager = NotificationSectionsFeatureManager(proxyFake, mContext)
manager!!.clearCache()
- originalQsMediaPlayer = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+ staticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic<Utils>(Utils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ `when`(Utils.useQsMediaPlayer(any())).thenReturn(false)
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0)
}
@After
public fun teardown() {
- Settings.Global.putInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsMediaPlayer)
+ staticMockSession.finishMocking()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index f4d8405..5c2f5fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -33,7 +32,6 @@
import android.app.NotificationManager;
import android.testing.AndroidTestingRunner;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -41,7 +39,6 @@
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.SectionClassifier;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -49,7 +46,6 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import org.junit.Before;
import org.junit.Test;
@@ -72,7 +68,6 @@
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
- @Mock private SectionHeaderController mSilentHeaderController;
@Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
@@ -94,7 +89,6 @@
mHighPriorityProvider,
mSectionClassifier,
mAlertingHeaderController,
- mSilentHeaderController,
mSilentNodeController);
mEntry = spy(new NotificationEntryBuilder().build());
mEntry.setRanking(getRankingForUnfilteredNotif().build());
@@ -112,25 +106,6 @@
}
@Test
- public void testSilentHeaderClearableChildrenUpdate() {
- ListEntry listEntry = new ListEntry(mEntry.getKey(), 0L) {
- @Nullable
- @Override
- public NotificationEntry getRepresentativeEntry() {
- return mEntry;
- }
- };
- setRankingAmbient(false);
- setSbnClearable(true);
- mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
- verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
-
- setSbnClearable(false);
- mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
- verify(mSilentHeaderController).setClearSectionEnabled(eq(false));
- }
-
- @Test
public void testUnfilteredState() {
// GIVEN no suppressed visual effects + app not suspended
mEntry.setRanking(getRankingForUnfilteredNotif().build());
@@ -225,46 +200,6 @@
assertInSection(mEntry, mSilentSectioner);
}
- @Test
- public void testClearableSilentSection() {
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
- setSbnClearable(true);
- setRankingAmbient(false);
- mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
- }
-
- @Test
- public void testClearableMinimizedSection() {
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
- setSbnClearable(true);
- setRankingAmbient(true);
- mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
- }
-
- @Test
- public void testNotClearableSilentSection() {
- setSbnClearable(false);
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
- setRankingAmbient(false);
- mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false));
- }
-
- @Test
- public void testNotClearableMinimizedSection() {
- setSbnClearable(false);
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
- setRankingAmbient(true);
- mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry));
- verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false));
- }
-
private void assertInSection(NotificationEntry entry, NotifSectioner section) {
for (NotifSectioner current: mSections) {
if (current == section) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
deleted file mode 100644
index a2d8e3d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2021 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.statusbar.notification.collection.coordinator
-
-import android.os.UserHandle
-import android.service.notification.StatusBarNotification
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.DynamicPrivacyController
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-class SensitiveContentCoordinatorTest : SysuiTestCase() {
-
- val dynamicPrivacyController: DynamicPrivacyController = mock()
- val lockscreenUserManager: NotificationLockscreenUserManager = mock()
- val pipeline: NotifPipeline = mock()
- val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
- val statusBarStateController: StatusBarStateController = mock()
- val keyguardStateController: KeyguardStateController = mock()
-
- val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent
- .factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController)
- .coordinator
-
- @Test
- fun onDynamicPrivacyChanged_invokeInvalidationListener() {
- coordinator.attach(pipeline)
- val invalidator = withArgCaptor<Invalidator> {
- verify(pipeline).addPreRenderInvalidator(capture())
- }
- val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
- verify(dynamicPrivacyController).addListener(capture())
- }
-
- val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
- invalidator.setInvalidationListener(invalidationListener)
-
- dynamicPrivacyListener.onDynamicPrivacyChanged()
-
- verify(invalidationListener).onPluggableInvalidated(invalidator)
- }
-
- @Test
- fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
- val entry = fakeNotification(1, false)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(false, false)
- }
-
- @Test
- fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
- val entry = fakeNotification(1, true)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(false, false)
- }
-
- @Test
- fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
- val entry = fakeNotification(1, false)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(false, false)
- }
-
- @Test
- fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
- val entry = fakeNotification(1, false)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(false, true)
- }
-
- @Test
- fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
- val entry = fakeNotification(1, true)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(true, true)
- }
-
- @Test
- fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
- val entry = fakeNotification(1, true)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(false, true)
- }
-
- @Test
- fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
- whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
-
- val entry = fakeNotification(2, true)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!).setSensitive(true, true)
- }
-
- @Test
- fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
- coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
-
- whenever(lockscreenUserManager.currentUserId).thenReturn(1)
- whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
- whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
- whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
- whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
- whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
- .thenReturn(true)
-
- val entry = fakeNotification(2, true)
-
- onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
- verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
- }
-
- private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
- val mockUserHandle = mock<UserHandle>().apply {
- whenever(identifier).thenReturn(notifUserId)
- }
- val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
- whenever(user).thenReturn(mockUserHandle)
- }
- val mockEntry = mock<NotificationEntry>().apply {
- whenever(sbn).thenReturn(mockSbn)
- }
- whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
- whenever(mockEntry.rowExists()).thenReturn(true)
- return object : ListEntry("key", 0) {
- override fun getRepresentativeEntry(): NotificationEntry = mockEntry
- }
- }
-}
-
-@CoordinatorScope
-@Component(modules = [SensitiveContentCoordinatorModule::class])
-interface TestSensitiveContentCoordinatorComponent {
- val coordinator: SensitiveContentCoordinator
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance dynamicPrivacyController: DynamicPrivacyController,
- @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
- @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
- @BindsInstance statusBarStateController: StatusBarStateController,
- @BindsInstance keyguardStateController: KeyguardStateController
- ): TestSensitiveContentCoordinatorComponent
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 70266e4..cf2fc7c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
@@ -43,6 +44,11 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class StackCoordinatorTest : SysuiTestCase() {
+
+ companion object {
+ const val NOTIF_USER_ID = 0
+ }
+
private lateinit var coordinator: StackCoordinator
private lateinit var afterRenderListListener: OnAfterRenderListListener
@@ -61,7 +67,10 @@
afterRenderListListener = withArgCaptor {
verify(pipeline).addOnAfterRenderListListener(capture())
}
- entry = NotificationEntryBuilder().setSection(section).build()
+ entry = NotificationEntryBuilder()
+ .setSection(section)
+ .setUser(UserHandle.of(NOTIF_USER_ID))
+ .build()
}
@Test
@@ -74,13 +83,31 @@
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController).setNotifStats(NotifStats(1, false, true, false, false))
+ verify(stackController)
+ .setNotifStats(
+ NotifStats(
+ 1,
+ false,
+ true,
+ false,
+ false,
+ setOf(NOTIF_USER_ID),
+ emptySet()))
}
@Test
fun testSetNotificationStats_clearableSilent() {
whenever(section.bucket).thenReturn(BUCKET_SILENT)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController).setNotifStats(NotifStats(1, false, false, false, true))
+ verify(stackController)
+ .setNotifStats(
+ NotifStats(
+ 1,
+ false,
+ false,
+ false,
+ true,
+ emptySet(),
+ setOf(NOTIF_USER_ID)))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index b63e66f..c7f7ec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.icon;
+package com.android.systemui.statusbar.notification.icon
-import android.app.ActivityManager;
-import android.app.Notification;
+import android.app.ActivityManager
+import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.Person
@@ -27,11 +27,12 @@
import android.graphics.drawable.Icon
import android.os.SystemClock
import android.os.UserHandle
-import android.testing.AndroidTestingRunner;
+import android.testing.AndroidTestingRunner
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -40,7 +41,7 @@
import org.junit.Before
import org.junit.Test
-import org.junit.runner.RunWith;
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
@@ -48,15 +49,14 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class IconManagerTest: SysuiTestCase() {
+class IconManagerTest : SysuiTestCase() {
companion object {
- private const val TEST_PACKAGE_NAME = "test";
- private const val TEST_UID = 0;
+ private const val TEST_PACKAGE_NAME = "test"
+ private const val TEST_UID = 0
}
-
private var id = 0
- private val context = InstrumentationRegistry.getTargetContext();
+ private val context = InstrumentationRegistry.getTargetContext()
@Mock private lateinit var shortcut: ShortcutInfo
@Mock private lateinit var shortcutIc: Icon
@Mock private lateinit var messageIc: Icon
@@ -65,6 +65,7 @@
@Mock private lateinit var drawable: Drawable
@Mock private lateinit var row: ExpandableNotificationRow
+ @Mock private lateinit var notifLockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var notifCollection: CommonNotifCollection
@Mock private lateinit var launcherApps: LauncherApps
@@ -83,13 +84,16 @@
`when`(shortcut.icon).thenReturn(shortcutIc)
`when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
+ `when`(notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(TEST_UID))
+ .thenReturn(true)
- iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+ iconManager =
+ IconManager(notifCollection, launcherApps, iconBuilder, notifLockscreenUserManager)
}
@Test
fun testCreateIcons_importantConversation_shortcutIcon() {
- val entry = notificationEntry(true, true, true)
+ val entry = notificationEntry()
entry?.channel?.isImportantConversation = true
entry?.let {
iconManager.createIcons(it)
@@ -99,7 +103,7 @@
@Test
fun testCreateIcons_importantConversation_messageIcon() {
- val entry = notificationEntry(false, true, true)
+ val entry = notificationEntry(hasShortcut = false)
entry?.channel?.isImportantConversation = true
entry?.let {
iconManager.createIcons(it)
@@ -109,7 +113,7 @@
@Test
fun testCreateIcons_importantConversation_largeIcon() {
- val entry = notificationEntry(false, false, true)
+ val entry = notificationEntry(hasShortcut = false, hasMessage = false)
entry?.channel?.isImportantConversation = true
entry?.let {
iconManager.createIcons(it)
@@ -119,7 +123,7 @@
@Test
fun testCreateIcons_importantConversation_smallIcon() {
- val entry = notificationEntry(false, false, false)
+ val entry = notificationEntry(hasShortcut = false, hasMessage = false, hasLargeIcon = false)
entry?.channel?.isImportantConversation = true
entry?.let {
iconManager.createIcons(it)
@@ -129,7 +133,7 @@
@Test
fun testCreateIcons_notImportantConversation() {
- val entry = notificationEntry(true, true, true)
+ val entry = notificationEntry()
entry?.let {
iconManager.createIcons(it)
}
@@ -138,8 +142,10 @@
@Test
fun testCreateIcons_sensitiveImportantConversation() {
- val entry = notificationEntry(true, false, false)
- entry?.setSensitive(true, true);
+ val entry = notificationEntry(
+ hasMessage = false,
+ hasLargeIcon = false,
+ hasSensitiveContent = true)
entry?.channel?.isImportantConversation = true
entry?.let {
iconManager.createIcons(it)
@@ -151,14 +157,17 @@
@Test
fun testUpdateIcons_sensitivityChange() {
- val entry = notificationEntry(true, false, false)
+ val entry = notificationEntry(
+ hasMessage = false,
+ hasLargeIcon = false,
+ hasSensitiveContent = true)
entry?.channel?.isImportantConversation = true
- entry?.setSensitive(true, true);
entry?.let {
iconManager.createIcons(it)
}
assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc)
- entry?.setSensitive(false, false);
+ `when`(notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(TEST_UID))
+ .thenReturn(false)
entry?.let {
iconManager.updateIcons(it)
}
@@ -166,14 +175,19 @@
}
private fun notificationEntry(
- hasShortcut: Boolean,
- hasMessage: Boolean,
- hasLargeIcon: Boolean
+ hasShortcut: Boolean = true,
+ hasMessage: Boolean = true,
+ hasLargeIcon: Boolean = true,
+ hasSensitiveContent: Boolean = false
): NotificationEntry? {
val n = Notification.Builder(mContext, "id")
.setSmallIcon(smallIc)
.setContentTitle("Title")
.setContentText("Text")
+ .setVisibility(
+ if (hasSensitiveContent)
+ Notification.VISIBILITY_PRIVATE
+ else Notification.VISIBILITY_PUBLIC)
if (hasMessage) {
n.style = Notification.MessagingStyle("")
@@ -203,7 +217,6 @@
val entry = builder.build()
entry.row = row
- entry.setSensitive(false, true);
return entry
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4ea9321..b3c34c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -91,7 +91,7 @@
@Test
public void testGroupSummaryNotShowingIconWhenPublic() {
- mGroupRow.setSensitive(true, true);
+ mGroupRow.setSensitive(true);
mGroupRow.setHideSensitiveForIntrinsicHeight(true);
assertTrue(mGroupRow.isSummaryWithChildren());
assertFalse(mGroupRow.isShowingIcon());
@@ -99,7 +99,7 @@
@Test
public void testNotificationHeaderVisibleWhenAnimating() {
- mGroupRow.setSensitive(true, true);
+ mGroupRow.setSensitive(true);
mGroupRow.setHideSensitive(true, false, 0, 0);
mGroupRow.setHideSensitive(false, true, 0, 0);
assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper()
@@ -130,7 +130,7 @@
public void testIconColorShouldBeUpdatedWhenSensitive() throws Exception {
ExpandableNotificationRow row = spy(mNotificationTestHelper.createRow(
FLAG_CONTENT_VIEW_ALL));
- row.setSensitive(true, true);
+ row.setSensitive(true);
row.setHideSensitive(true, false, 0, 0);
verify(row).updateShelfIconColor();
}
@@ -214,7 +214,7 @@
@Test
public void testFeedback_noHeader() {
// public notification is custom layout - no header
- mGroupRow.setSensitive(true, true);
+ mGroupRow.setSensitive(true);
mGroupRow.setOnFeedbackClickListener(null);
mGroupRow.setFeedbackIcon(null);
}
@@ -318,15 +318,6 @@
}
@Test
- public void testGetIsNonblockable_oemLocked() throws Exception {
- ExpandableNotificationRow row =
- mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
- row.getEntry().getChannel().setImportanceLockedByOEM(true);
-
- assertTrue(row.getIsNonblockable());
- }
-
- @Test
public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
ExpandableNotificationRow row =
mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 251ac7d..a855705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -78,7 +78,6 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
@@ -132,7 +131,6 @@
@Mock private NotificationEntryListener mEntryListener;
@Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
@Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationGutsManager mGutsManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@@ -254,6 +252,7 @@
.thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
new ExpandableNotificationRowController(
viewCaptor.getValue(),
+ mLockscreenUserManager,
mock(ActivatableNotificationViewController.class),
mock(RemoteInputViewSubcomponent.Factory.class),
mock(MetricsLogger.class),
@@ -300,7 +299,8 @@
new IconManager(
mEntryManager,
mock(LauncherApps.class),
- new IconBuilder(mContext)),
+ new IconBuilder(mContext),
+ mLockscreenUserManager),
mock(LowPriorityInflationHelper.class),
mNotifPipelineFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 1ecb09b..d5ed37a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -52,6 +52,7 @@
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -152,7 +153,8 @@
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
- new IconBuilder(mContext));
+ new IconBuilder(mContext),
+ mock(NotificationLockscreenUserManager.class));
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 4270d72..3f19036 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -5,8 +5,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationShelf
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import junit.framework.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -143,6 +144,113 @@
assertFalse(isYBelowShelfInView)
}
+ @Test
+ fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() {
+ val shelfClipStart = 0f
+ val viewStart = 1f
+
+ val expandableView = mock(ExpandableView::class.java)
+ whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
+ whenever(expandableView.translationY).thenReturn(viewStart)
+ whenever(expandableView.actualHeight).thenReturn(20)
+
+ whenever(expandableView.minHeight).thenReturn(20)
+ whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
+ whenever(expandableView.isInShelf).thenReturn(true)
+
+ whenever(ambientState.isOnKeyguard).thenReturn(true)
+ whenever(ambientState.isExpansionChanging).thenReturn(false)
+ whenever(ambientState.isShadeExpanded).thenReturn(true)
+
+ val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
+ /* view= */ expandableView,
+ /* scrollingFast= */ false,
+ /* expandingAnimated= */ false,
+ /* isLastChild= */ true,
+ shelfClipStart)
+ assertEquals(1f, amountInShelf)
+ }
+
+ @Test
+ fun getAmountInShelf_lastViewAlmostBelowShelf_completelyInShelf() {
+ val viewStart = 0f
+ val shelfClipStart = 0.001f
+
+ val expandableView = mock(ExpandableView::class.java)
+ whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
+ whenever(expandableView.translationY).thenReturn(viewStart)
+ whenever(expandableView.actualHeight).thenReturn(20)
+
+ whenever(expandableView.minHeight).thenReturn(20)
+ whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
+ whenever(expandableView.isInShelf).thenReturn(true)
+
+ whenever(ambientState.isOnKeyguard).thenReturn(true)
+ whenever(ambientState.isExpansionChanging).thenReturn(false)
+ whenever(ambientState.isShadeExpanded).thenReturn(true)
+
+ val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
+ /* view= */ expandableView,
+ /* scrollingFast= */ false,
+ /* expandingAnimated= */ false,
+ /* isLastChild= */ true,
+ shelfClipStart)
+ assertEquals(1f, amountInShelf)
+ }
+
+ @Test
+ fun getAmountInShelf_lastViewHalfClippedByShelf_halfInShelf() {
+ val viewStart = 0f
+ val shelfClipStart = 10f
+
+ val expandableView = mock(ExpandableView::class.java)
+ whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
+ whenever(expandableView.translationY).thenReturn(viewStart)
+ whenever(expandableView.actualHeight).thenReturn(25)
+
+ whenever(expandableView.minHeight).thenReturn(25)
+ whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
+ whenever(expandableView.isInShelf).thenReturn(true)
+
+ whenever(ambientState.isOnKeyguard).thenReturn(true)
+ whenever(ambientState.isExpansionChanging).thenReturn(false)
+ whenever(ambientState.isShadeExpanded).thenReturn(true)
+
+ val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
+ /* view= */ expandableView,
+ /* scrollingFast= */ false,
+ /* expandingAnimated= */ false,
+ /* isLastChild= */ true,
+ shelfClipStart)
+ assertEquals(0.5f, amountInShelf)
+ }
+
+ @Test
+ fun getAmountInShelf_lastViewAboveShelf_notInShelf() {
+ val viewStart = 0f
+ val shelfClipStart = 15f
+
+ val expandableView = mock(ExpandableView::class.java)
+ whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
+ whenever(expandableView.translationY).thenReturn(viewStart)
+ whenever(expandableView.actualHeight).thenReturn(10)
+
+ whenever(expandableView.minHeight).thenReturn(10)
+ whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
+ whenever(expandableView.isInShelf).thenReturn(false)
+
+ whenever(ambientState.isExpansionChanging).thenReturn(false)
+ whenever(ambientState.isOnKeyguard).thenReturn(true)
+
+ val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
+ /* view= */ expandableView,
+ /* scrollingFast= */ false,
+ /* expandingAnimated= */ false,
+ /* isLastChild= */ true,
+ shelfClipStart)
+ assertEquals(0f, amountInShelf)
+ }
+
private fun setFractionToShade(fraction: Float) {
whenever(ambientState.fractionToShade).thenReturn(fraction)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index e8608fa7..63e0f53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -31,6 +31,8 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -612,6 +614,12 @@
assertTrue(mStackScroller.isInsideQsHeader(event2));
}
+ @Test
+ public void setFractionToShade_recomputesStackHeight() {
+ mStackScroller.setFractionToShade(1f);
+ verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index ed22cd3..169c04c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,9 +58,12 @@
private final NotificationStackScrollLayoutController mStackScrollerController =
mock(NotificationStackScrollLayoutController.class);
- private final NotificationPanelViewController mPanelView =
+ private final NotificationPanelViewController mPanelViewController =
mock(NotificationPanelViewController.class);
private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
+ private final NotificationLockscreenUserManager mLockscreenUserManager =
+ mock(NotificationLockscreenUserManager.class);
+
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private ExpandableNotificationRow mFirst;
private HeadsUpStatusBarView mHeadsUpStatusBarView;
@@ -93,12 +97,13 @@
mHeadsUpManager,
mStatusbarStateController,
mBypassController,
+ mLockscreenUserManager,
mWakeUpCoordinator,
mDarkIconDispatcher,
mKeyguardStateController,
mCommandQueue,
mStackScrollerController,
- mPanelView,
+ mPanelViewController,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.of(mOperatorNameView));
@@ -175,12 +180,13 @@
mHeadsUpManager,
mStatusbarStateController,
mBypassController,
+ mLockscreenUserManager,
mWakeUpCoordinator,
mDarkIconDispatcher,
mKeyguardStateController,
mCommandQueue,
mStackScrollerController,
- mPanelView,
+ mPanelViewController,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.empty());
@@ -193,15 +199,15 @@
public void testDestroy() {
reset(mHeadsUpManager);
reset(mDarkIconDispatcher);
- reset(mPanelView);
+ reset(mPanelViewController);
reset(mStackScrollerController);
mHeadsUpAppearanceController.onViewDetached();
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mPanelView).removeTrackingHeadsUpListener(any());
- verify(mPanelView).setHeadsUpAppearanceController(isNull());
+ verify(mPanelViewController).removeTrackingHeadsUpListener(any());
+ verify(mPanelViewController).setHeadsUpAppearanceController(isNull());
verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 6d3a5fe..ec20271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -336,7 +336,7 @@
// WHEN the position algorithm is run
positionClock();
// THEN the padding DOESN'T adjust for keyguard status height.
- assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
+ assertThat(mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding())
.isEqualTo(mKeyguardStatusBarHeaderHeight);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 94e6b9a..356d002 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -610,13 +610,13 @@
when(mLockIconViewController.getTop()).thenReturn(80f);
when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5);
- // Available space (100 - 10 - 15 = 75)
+ // Available space (100 - 0 - 15 = 85)
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100);
- when(mNotificationStackScrollLayoutController.getTopPadding()).thenReturn(10);
+ when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0);
mNotificationPanelViewController.updateResources();
assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications())
- .isEqualTo(75);
+ .isEqualTo(85);
}
@Test
@@ -1009,6 +1009,17 @@
}
@Test
+ public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
+ enableSplitShade(/* enabled= */ true);
+ mStatusBarStateController.setState(KEYGUARD);
+
+ mNotificationPanelViewController.expandWithQs();
+
+ verify(mLockscreenShadeTransitionController).goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
+ }
+
+ @Test
public void testUnlockAnimationDoesNotAffectScrim() {
mNotificationPanelViewController.onUnlockHintStarted();
verify(mScrimController).setExpansionAffectsAlpha(false);
diff --git a/services/Android.bp b/services/Android.bp
index 1e4ce19..70692a6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -102,7 +102,6 @@
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
- ":services.selectiontoolbar-sources",
":services.smartspace-sources",
":services.speech-sources",
":services.systemcaptions-sources",
@@ -158,7 +157,6 @@
"services.profcollect",
"services.restrictions",
"services.searchui",
- "services.selectiontoolbar",
"services.smartspace",
"services.speech",
"services.systemcaptions",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 649328d..9b29bae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -242,6 +242,9 @@
int getCurrentUserIdLocked();
+ Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int windowId);
+
boolean isAccessibilityButtonShown();
/**
@@ -551,8 +554,6 @@
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -576,11 +577,11 @@
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -628,8 +629,6 @@
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float [] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -653,11 +652,11 @@
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -706,8 +705,6 @@
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -731,11 +728,11 @@
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -786,8 +783,6 @@
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return null;
@@ -811,11 +806,11 @@
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -865,8 +860,6 @@
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return null;
@@ -889,12 +882,11 @@
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
-
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -1672,21 +1664,10 @@
mInvocationHandler.startInputLocked(connection, editorInfo, restarting);
}
-
-
@Nullable
- Pair<float[], MagnificationSpec> getTransformMatrixAndSpecLocked(int resolvedWindowId) {
- final WindowInfo windowInfo =
- mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId);
- if (windowInfo == null) {
- Slog.w(LOG_TAG, "getTransformMatrixAndSpec, windowInfo is null window id = "
- + resolvedWindowId);
- return new Pair<>(null, null);
- }
-
- final MagnificationSpec spec = new MagnificationSpec();
- spec.setTo(windowInfo.mMagnificationSpec);
- return new Pair<>(windowInfo.mTransformMatrix, spec);
+ private Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int resolvedWindowId) {
+ return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4806514..99c8495 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -68,6 +68,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -99,6 +100,7 @@
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArraySet;
import android.util.IntArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -458,6 +460,41 @@
}
@Override
+ public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int windowId) {
+ WindowInfo windowInfo;
+ synchronized (mLock) {
+ windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(windowId);
+ }
+ if (windowInfo != null) {
+ final MagnificationSpec spec = new MagnificationSpec();
+ spec.setTo(windowInfo.mMagnificationSpec);
+ return new Pair<>(windowInfo.mTransformMatrix, spec);
+ } else {
+ // If the framework doesn't track windows, we fall back to get the pair of
+ // transformation matrix and MagnificationSpe from the WindowManagerService's
+ // WindowState.
+ IBinder token;
+ synchronized (mLock) {
+ token = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(mCurrentUserId,
+ windowId);
+ }
+ Pair<Matrix, MagnificationSpec> pair =
+ mWindowManagerService.getWindowTransformationMatrixAndMagnificationSpec(token);
+ final float[] outTransformationMatrix = new float[9];
+ final Matrix tmpMatrix = pair.first;
+ final MagnificationSpec spec = pair.second;
+ if (!spec.isNop()) {
+ tmpMatrix.postScale(spec.scale, spec.scale);
+ tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+ }
+ tmpMatrix.getValues(outTransformationMatrix);
+
+ return new Pair<>(outTransformationMatrix, pair.second);
+ }
+ }
+
+ @Override
public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
userState.mBoundServices);
@@ -3750,12 +3787,12 @@
boundsInScreenBeforeMagnification.centerY());
// Invert magnification if needed.
- final WindowInfo windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(
- focus.getWindowId());
+ final Pair<float[], MagnificationSpec> pair =
+ getWindowTransformationMatrixAndMagnificationSpec(focus.getWindowId());
MagnificationSpec spec = null;
- if (windowInfo != null) {
+ if (pair != null && pair.second != null) {
spec = new MagnificationSpec();
- spec.setTo(windowInfo.mMagnificationSpec);
+ spec.setTo(pair.second);
}
if (spec != null && !spec.isNop()) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f18d13d..6d3620f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1559,9 +1559,18 @@
Slog.e(TAG, "Error sending input show up notification", e);
}
}
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestFallbackFromFillDialog() {
+ setFillDialogDisabled();
synchronized (mLock) {
- // stop to show fill dialog
- mSessionFlags.mFillDialogDisabled = true;
+ if (mCurrentViewId == null) {
+ return;
+ }
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.maybeCallOnFillReady(mFlags);
}
}
@@ -3208,16 +3217,24 @@
return;
}
- if (requestShowFillDialog(response, filledId, filterText, flags)) {
- synchronized (mLock) {
- final ViewState currentView = mViewStates.get(mCurrentViewId);
- currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
- mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ if (ids != null && ArrayUtils.contains(ids, filledId)) {
+ if (requestShowFillDialog(response, filledId, filterText, flags)) {
+ synchronized (mLock) {
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+ mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+ }
+ // Just show fill dialog once, so disabled after shown.
+ // Note: Cannot disable before requestShowFillDialog() because the method
+ // need to check whether fill dialog enabled.
+ setFillDialogDisabled();
+ return;
+ } else {
+ setFillDialogDisabled();
}
- return;
- }
- setFillDialogDisabled();
+ }
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
@@ -3324,15 +3341,11 @@
return false;
}
- final AutofillId[] ids = response.getFillDialogTriggerIds();
- if (ids == null || !ArrayUtils.contains(ids, filledId)) {
- return false;
- }
-
final Drawable serviceIcon = getServiceIcon();
getUiForShowing().showFillDialog(filledId, response, filterText,
- mService.getServicePackageName(), mComponentName, serviceIcon, this);
+ mService.getServicePackageName(), mComponentName, serviceIcon, this,
+ id, mCompatMode);
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 57768ef..3ab873d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -97,6 +97,7 @@
void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
void cancelSession();
void requestShowSoftInput(AutofillId id);
+ void requestFallbackFromFillDialog();
}
public AutoFillUI(@NonNull Context context) {
@@ -388,13 +389,19 @@
public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
@Nullable String filterText, @Nullable String servicePackageName,
@NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
- @NonNull AutoFillUiCallback callback) {
+ @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode) {
if (sVerbose) {
Slog.v(TAG, "showFillDialog for "
+ componentName.toShortString() + ": " + response);
}
- // TODO: enable LogMaker
+ final LogMaker log = Helper
+ .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
+ sessionId, compatMode)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+ filterText == null ? 0 : filterText.length())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+ response.getDatasets() == null ? 0 : response.getDatasets().size());
mHandler.post(() -> {
if (callback != mCallback) {
@@ -406,6 +413,7 @@
mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
@Override
public void onResponsePicked(FillResponse response) {
+ log(MetricsEvent.TYPE_DETAIL);
hideFillDialogUiThread(callback);
if (mCallback != null) {
mCallback.authenticate(response.getRequestId(),
@@ -417,6 +425,7 @@
@Override
public void onDatasetPicked(Dataset dataset) {
+ log(MetricsEvent.TYPE_ACTION);
hideFillDialogUiThread(callback);
if (mCallback != null) {
final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -426,15 +435,29 @@
}
@Override
- public void onCanceled() {
+ public void onDismissed() {
+ log(MetricsEvent.TYPE_DISMISS);
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
}
@Override
+ public void onCanceled() {
+ log(MetricsEvent.TYPE_CLOSE);
+ hideFillDialogUiThread(callback);
+ callback.requestShowSoftInput(focusedId);
+ callback.requestFallbackFromFillDialog();
+ }
+
+ @Override
public void startIntentSender(IntentSender intentSender) {
mCallback.startIntentSenderAndFinishSession(intentSender);
}
+
+ private void log(int type) {
+ log.setType(type);
+ mMetricsLogger.write(log);
+ }
});
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index f9f5289..5a1a1ae 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -81,6 +81,7 @@
interface UiCallback {
void onResponsePicked(@NonNull FillResponse response);
void onDatasetPicked(@NonNull Dataset dataset);
+ void onDismissed();
void onCanceled();
void startIntentSender(IntentSender intentSender);
}
@@ -144,6 +145,7 @@
mDialog = new Dialog(mContext, mThemeId);
mDialog.setContentView(decor);
setDialogParamsAsBottomSheet();
+ mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
show();
}
@@ -220,7 +222,7 @@
final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
// set "No thinks" by default
noButton.setText(R.string.autofill_save_no);
- noButton.setOnClickListener((v) -> mCallback.onCanceled());
+ noButton.setOnClickListener((v) -> mCallback.onDismissed());
}
private void setContinueButton(View decor, View.OnClickListener listener) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 62bb9f1..9b2554f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -62,7 +62,6 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
@@ -81,7 +80,6 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.BidiFormatter;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Log;
@@ -294,14 +292,13 @@
private boolean onCompanionApplicationBindingDiedInternal(
@UserIdInt int userId, @NonNull String packageName) {
- // Update the current connected devices sets when binderDied, so that application is able
- // to call notifyDeviceAppeared after re-launch the application.
for (AssociationInfo ai :
mAssociationStore.getAssociationsForPackage(userId, packageName)) {
- int id = ai.getId();
- Slog.i(TAG, "Removing association id: " + id + " for package: "
- + packageName + " due to binderDied.");
- mDevicePresenceMonitor.removeDeviceFromMonitoring(id);
+ final int associationId = ai.getId();
+ if (ai.isSelfManaged()
+ && mDevicePresenceMonitor.isDevicePresent(associationId)) {
+ mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
+ }
}
// TODO(b/218613015): implement.
return false;
@@ -538,20 +535,12 @@
String callingPackage = component.getPackageName();
checkCanCallNotificationApi(callingPackage);
// TODO: check userId.
- String packageTitle = BidiFormatter.getInstance().unicodeWrap(
- getPackageInfo(getContext(), userId, callingPackage)
- .applicationInfo
- .loadSafeLabel(getContext().getPackageManager(),
- PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
- PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
- .toString());
final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
NotificationAccessConfirmationActivityContract.launcherIntent(
- getContext(), userId, component, packageTitle),
+ getContext(), userId, component),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
@@ -732,9 +721,12 @@
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
- new CompanionDeviceShellCommand(
- CompanionDeviceManagerService.this, mAssociationStore)
- .exec(this, in, out, err, args, callback, resultReceiver);
+
+ final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
+ CompanionDeviceManagerService.this,
+ mAssociationStore,
+ mDevicePresenceMonitor);
+ cmd.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index fd13085..6a19a42 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -21,6 +21,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+
import java.io.PrintWriter;
import java.util.List;
@@ -29,20 +31,24 @@
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
- AssociationStore associationStore) {
+ AssociationStore associationStore,
+ CompanionDevicePresenceMonitor devicePresenceMonitor) {
mService = service;
mAssociationStore = associationStore;
+ mDevicePresenceMonitor = devicePresenceMonitor;
}
@Override
public int onCommand(String cmd) {
final PrintWriter out = getOutPrintWriter();
+ final int associationId;
try {
switch (cmd) {
case "list": {
- final int userId = getNextArgInt();
+ final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getAssociationsForUser(userId);
for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@
break;
case "associate": {
- int userId = getNextArgInt();
+ int userId = getNextIntArgRequired();
String packageName = getNextArgRequired();
String address = getNextArgRequired();
mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@
break;
case "disassociate": {
- final int userId = getNextArgInt();
+ final int userId = getNextIntArgRequired();
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
@@ -80,6 +86,16 @@
}
break;
+ case "simulate-device-appeared":
+ associationId = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
+ break;
+
+ case "simulate-device-disappeared":
+ associationId = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceDisappeared(associationId);
+ break;
+
default:
return handleDefaultCommands(cmd);
}
@@ -91,10 +107,6 @@
}
}
- private int getNextArgInt() {
- return Integer.parseInt(getNextArgRequired());
- }
-
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -108,7 +120,31 @@
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
pw.println(" clear-association-memory-cache");
- pw.println(" Clear the in-memory association cache and reload all association "
- + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" Clear the in-memory association cache and reload all association ");
+ pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-appeared ASSOCIATION_ID");
+ pw.println(" Make CDM act as if the given companion device has appeared.");
+ pw.println(" I.e. bind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
+ pw.println(" The CDM will consider the devices as present for 60 seconds and then");
+ pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'");
+ pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out"
+ + ".");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-disappeared ASSOCIATION_ID");
+ pw.println(" Make CDM act as if the given companion device has disappeared.");
+ pw.println(" I.e. unbind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
+ pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was");
+ pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
+ pw.println(" 60 seconds ago.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ }
+
+ private int getNextIntArgRequired() {
+ return Integer.parseInt(getNextArgRequired());
}
}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 24be1b6..89ed301e 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,11 +16,19 @@
package com.android.server.companion.presence;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.bluetooth.BluetoothAdapter;
import android.companion.AssociationInfo;
import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import com.android.server.companion.AssociationStore;
@@ -72,6 +80,11 @@
private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
@NonNull Callback callback) {
mAssociationStore = associationStore;
@@ -106,7 +119,8 @@
public boolean isDevicePresent(int associationId) {
return mReportedSelfManagedDevices.contains(associationId)
|| mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId);
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
}
/**
@@ -135,6 +149,13 @@
onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
}
+ /**
+ * Marks a "self-managed" device as disconnected when binderDied.
+ */
+ public void onSelfManagedDeviceReporterBinderDied(int associationId) {
+ onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
+ }
+
@Override
public void onBluetoothCompanionDeviceConnected(int associationId) {
onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
@@ -155,6 +176,45 @@
onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
}
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceAppeared(int associationId) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceDisappeared(int associationId) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+
+ onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
if (DEBUG) {
@@ -206,15 +266,6 @@
}
/**
- * Remove the current connected devices by associationId.
- */
- public void removeDeviceFromMonitoring(int associationId) {
- mConnectedBtDevices.remove(associationId);
- mNearbyBleDevices.remove(associationId);
- mReportedSelfManagedDevices.remove(associationId);
- }
-
- /**
* Implements
* {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
*/
@@ -226,10 +277,44 @@
Log.d(TAG, " > association=" + association);
}
- removeDeviceFromMonitoring(id);
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
// Do NOT call mCallback.onDeviceDisappeared()!
// CompanionDeviceManagerService will know that the association is removed, and will do
// what's needed.
}
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 9d4b50b..80182d2 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
@@ -29,11 +30,13 @@
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Display;
+import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,7 +47,11 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/** Controls virtual input devices, including device lifecycle and event dispatch. */
class InputController {
@@ -72,20 +79,27 @@
@GuardedBy("mLock")
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+ private final Handler mHandler;
private final NativeWrapper mNativeWrapper;
private final DisplayManagerInternal mDisplayManagerInternal;
private final InputManagerInternal mInputManagerInternal;
+ private final DeviceCreationThreadVerifier mThreadVerifier;
- InputController(@NonNull Object lock) {
- this(lock, new NativeWrapper());
+ InputController(@NonNull Object lock, @NonNull Handler handler) {
+ this(lock, new NativeWrapper(), handler,
+ // Verify that virtual devices are not created on the handler thread.
+ () -> !handler.getLooper().isCurrentThread());
}
@VisibleForTesting
- InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+ InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
+ @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
mLock = lock;
+ mHandler = handler;
mNativeWrapper = nativeWrapper;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mThreadVerifier = threadVerifier;
}
void close() {
@@ -108,23 +122,13 @@
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_KEYBOARD);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating keyboard: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual keyboard", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual keyboard device '" + deviceName + "'.", e);
}
}
@@ -134,25 +138,15 @@
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_MOUSE);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating mouse: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
- mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual mouse", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
+ deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual mouse device: '" + deviceName + "'.", e);
}
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
}
void createTouchscreen(@NonNull String deviceName,
@@ -162,24 +156,14 @@
int displayId,
@NonNull Point screenSize) {
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
- screenSize.y, screenSize.x);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating touchscreen: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual touchscreen", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ phys, screenSize.y, screenSize.x));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
}
}
@@ -510,4 +494,133 @@
unregisterInputDevice(mDeviceToken);
}
}
+
+ /** A helper class used to wait for an input device to be registered. */
+ private class WaitForDevice implements AutoCloseable {
+ private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+ private final InputManager.InputDeviceListener mListener;
+
+ WaitForDevice(String deviceName, int vendorId, int productId) {
+ mListener = new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ final InputDevice device = InputManager.getInstance().getInputDevice(
+ deviceId);
+ Objects.requireNonNull(device, "Newly added input device was null.");
+ if (!device.getName().equals(deviceName)) {
+ return;
+ }
+ final InputDeviceIdentifier id = device.getIdentifier();
+ if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+ return;
+ }
+ mDeviceAddedLatch.countDown();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+
+ }
+ };
+ InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
+ }
+
+ /** Note: This must not be called from {@link #mHandler}'s thread. */
+ void waitForDeviceCreation() throws DeviceCreationException {
+ try {
+ if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
+ throw new DeviceCreationException(
+ "Timed out waiting for virtual device to be created.");
+ }
+ } catch (InterruptedException e) {
+ throw new DeviceCreationException(
+ "Interrupted while waiting for virtual device to be created.", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ InputManager.getInstance().unregisterInputDeviceListener(mListener);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual device. */
+ private static class DeviceCreationException extends Exception {
+ DeviceCreationException(String message) {
+ super(message);
+ }
+ DeviceCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Creates a virtual input device synchronously, and waits for the notification that the device
+ * was added.
+ *
+ * Note: Input device creation is expected to happen on a binder thread, and the calling thread
+ * will be blocked until the input device creation is successful. This should not be called on
+ * the handler's thread.
+ *
+ * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
+ * process of creating the device. This method will take care
+ * to restore the state of the system in the event of any
+ * unexpected behavior.
+ */
+ private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
+ int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
+ Supplier<Integer> deviceOpener)
+ throws DeviceCreationException {
+ if (!mThreadVerifier.isValidThread()) {
+ throw new IllegalStateException(
+ "Virtual device creation should happen on an auxiliary thread (e.g. binder "
+ + "thread) and not from the handler's thread.");
+ }
+
+ final int fd;
+ final BinderDeathRecipient binderDeathRecipient;
+
+ setUniqueIdAssociation(displayId, phys);
+ try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+ fd = deviceOpener.get();
+ if (fd < 0) {
+ throw new DeviceCreationException(
+ "A native error occurred when creating touchscreen: " + -fd);
+ }
+ // The fd is valid from here, so ensure that all failures close the fd after this point.
+ try {
+ waiter.waitForDeviceCreation();
+
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ try {
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new DeviceCreationException(
+ "Client died before virtual device could be created.", e);
+ }
+ } catch (DeviceCreationException e) {
+ mNativeWrapper.closeUinput(fd);
+ throw e;
+ }
+ } catch (DeviceCreationException e) {
+ InputManager.getInstance().removeUniqueIdAssociation(phys);
+ throw e;
+ }
+
+ synchronized (mLock) {
+ mInputDeviceDescriptors.put(deviceToken,
+ new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
+ }
+ }
+
+ @VisibleForTesting
+ interface DeviceCreationThreadVerifier {
+ /** Returns true if the calling thread is a valid thread for device creation. */
+ boolean isValidThread();
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index de14ef6..9802b97 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -166,7 +166,8 @@
mAppToken = token;
mParams = params;
if (inputController == null) {
- mInputController = new InputController(mVirtualDeviceLock);
+ mInputController = new InputController(
+ mVirtualDeviceLock, context.getMainThreadHandler());
} else {
mInputController = inputController;
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 06f698e..ed61100 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -334,10 +334,14 @@
/**
* Retrieve all receivers that can handle a broadcast of the given intent.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @param forSend true if the invocation is intended for sending broadcasts. The value
+ * of this parameter affects how packages are filtered.
*/
public abstract List<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int filterCallingUid, int userId);
+ int filterCallingUid, int userId, boolean forSend);
/**
* Retrieve all services that can be performed for the given intent.
@@ -371,10 +375,10 @@
int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
/**
- * Called by Owners to set the package names protected by the device owner.
+ * Marks packages as protected for a given user or all users in case of USER_ALL.
*/
- public abstract void setDeviceOwnerProtectedPackages(
- String deviceOwnerPackageName, List<String> packageNames);
+ public abstract void setOwnerProtectedPackages(
+ @UserIdInt int userId, @NonNull List<String> packageNames);
/**
* Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index bc40170..5eec6e5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3880,9 +3880,12 @@
match = vol.isVisibleForWrite(userId)
|| (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia));
} else {
+ // Return both read only and write only volumes. When includeSharedProfile is
+ // true, all the volumes of userIdSharingMedia should be returned when queried
+ // from the user it shares media with
match = vol.isVisibleForUser(userId)
|| (!vol.isVisible() && includeInvisible && vol.getPath() != null)
- || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia));
+ || (includeSharedProfile && vol.isVisibleForUser(userIdSharingMedia));
}
if (!match) continue;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dea8cc0..f6e8bc8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2627,7 +2627,7 @@
public void batterySendBroadcast(Intent intent) {
synchronized (this) {
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null,
- OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
+ null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.USER_ALL);
}
}
@@ -4241,7 +4241,7 @@
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
broadcastIntentLocked(null, null, null, intent,
- null, null, 0, null, null, null, null, OP_NONE,
+ null, null, 0, null, null, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.getUserId(uid));
}
@@ -8122,7 +8122,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, null, intent,
- null, null, 0, null, null, null, null, OP_NONE,
+ null, null, 0, null, null, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
currentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -8134,8 +8134,8 @@
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered, boolean sticky,
int sendingUser) {}
- }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+ }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, null,
+ OP_NONE, null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
UserHandle.USER_ALL);
} catch (Throwable e) {
Slog.wtf(TAG, "Failed sending first user broadcasts", e);
@@ -13165,8 +13165,8 @@
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
- null, null, -1, -1, false, null, null, null, OP_NONE, null, receivers,
- null, 0, null, null, false, true, true, -1, false, null,
+ null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
+ receivers, null, 0, null, null, false, true, true, -1, false, null,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
@@ -13248,8 +13248,8 @@
UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
continue;
}
- List<ResolveInfo> newReceivers = mPackageManagerInt
- .queryIntentReceivers(intent, resolvedType, pmFlags, callingUid, user);
+ List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
+ intent, resolvedType, pmFlags, callingUid, user, true /* forSend */);
if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
// If this is not the system user, we need to check for
// any receivers that should be filtered out.
@@ -13265,8 +13265,9 @@
if (newReceivers != null) {
for (int i = newReceivers.size() - 1; i >= 0; i--) {
final ResolveInfo ri = newReceivers.get(i);
- final Resolution<ResolveInfo> resolution = mComponentAliasResolver
- .resolveReceiver(intent, ri, resolvedType, pmFlags, user, callingUid);
+ final Resolution<ResolveInfo> resolution =
+ mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
+ pmFlags, user, callingUid, true /* forSend */);
if (resolution == null) {
// It was an alias, but the target was not found.
newReceivers.remove(i);
@@ -13421,12 +13422,14 @@
String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, String[] excludedPermissions,
- int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid,
+ String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
+ boolean sticky, int callingPid,
int callingUid, int realCallingUid, int realCallingPid, int userId) {
return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
- excludedPermissions, appOp, bOptions, ordered, sticky, callingPid, callingUid,
- realCallingUid, realCallingPid, userId, false /* allowBackgroundActivityStarts */,
+ excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid,
+ callingUid, realCallingUid, realCallingPid, userId,
+ false /* allowBackgroundActivityStarts */,
null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
}
@@ -13435,7 +13438,7 @@
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions,
- String[] excludedPermissions, int appOp, Bundle bOptions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@@ -14042,10 +14045,10 @@
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers,
- resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId,
- allowBackgroundActivityStarts, backgroundActivityStartsToken,
- timeoutExempt);
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+ registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
+ sticky, false, userId, allowBackgroundActivityStarts,
+ backgroundActivityStartsToken, timeoutExempt);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending
&& (queue.replaceParallelBroadcastLocked(r) != null);
@@ -14140,7 +14143,7 @@
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, appOp, brOptions,
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
receivers, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId, allowBackgroundActivityStarts,
backgroundActivityStartsToken, timeoutExempt);
@@ -14269,14 +14272,16 @@
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
return broadcastIntentWithFeature(caller, null, intent, resolvedType, resultTo, resultCode,
- resultData, resultExtras, requiredPermissions, null, appOp, bOptions, serialized,
- sticky, userId);
+ resultData, resultExtras, requiredPermissions, null, null, appOp, bOptions,
+ serialized, sticky, userId);
}
+ @Override
public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
- String[] requiredPermissions, String[] excludedPermissions, int appOp, Bundle bOptions,
+ String[] requiredPermissions, String[] excludedPermissions,
+ String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
@@ -14291,8 +14296,8 @@
return broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, excludedPermissions, appOp, bOptions, serialized,
- sticky, callingPid, callingUid, callingUid, callingPid, userId);
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
+ serialized, sticky, callingPid, callingUid, callingUid, callingPid, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -14315,7 +14320,7 @@
try {
return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
- OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
+ null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
realCallingPid, userId, allowBackgroundActivityStarts,
backgroundActivityStartsToken, broadcastAllowList);
} finally {
@@ -16834,10 +16839,11 @@
return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
null /*callerPackage*/, null /*callingFeatureId*/, intent,
null /*resolvedType*/, resultTo, 0 /*resultCode*/, null /*resultData*/,
- null /*resultExtras*/, requiredPermissions, null, AppOpsManager.OP_NONE,
- bOptions /*options*/, serialized, false /*sticky*/, callingPid,
- callingUid, callingUid, callingPid, userId,
- false /*allowBackgroundStarts*/,
+ null /*resultExtras*/, requiredPermissions,
+ null /*excludedPermissions*/, null /*excludedPackages*/,
+ AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
+ false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
+ userId, false /*allowBackgroundStarts*/,
null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -16973,7 +16979,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -16988,8 +16994,8 @@
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_LOCALE_CHANGED, "");
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
+ SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.USER_ALL);
}
@@ -17004,8 +17010,9 @@
String[] permissions =
new String[] { android.Manifest.permission.INSTALL_PACKAGES };
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
- permissions, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
- Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
+ permissions, null, null, OP_NONE, null, false, false, MY_PID,
+ SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.USER_ALL);
}
}
}
@@ -17029,8 +17036,8 @@
}
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
- Binder.getCallingPid(), UserHandle.USER_ALL);
+ null, null, OP_NONE, null, false, false, -1, SYSTEM_UID,
+ Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
}
}
@@ -17195,17 +17202,17 @@
}
@Override
- public boolean isUidCurrentlyInstrumented(int uid) {
+ public int getInstrumentationSourceUid(int uid) {
synchronized (mProcLock) {
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
if (!activeInst.mFinished && activeInst.mTargetInfo != null
&& activeInst.mTargetInfo.uid == uid) {
- return true;
+ return activeInst.mSourceUid;
}
}
}
- return false;
+ return INVALID_UID;
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 402491d..397a442 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -804,8 +804,8 @@
pw.flush();
Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle();
mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null,
- requiredPermissions, null, android.app.AppOpsManager.OP_NONE, bundle, true, false,
- mUserId);
+ requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, bundle, true,
+ false, mUserId);
if (!mAsync) {
receiver.waitForFinish();
}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6e28d8f..c09bb2d 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -56,7 +56,6 @@
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Build;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.SystemClock;
@@ -68,6 +67,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -97,7 +97,7 @@
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE =
- DEBUG_BACKGROUND_BATTERY_TRACKER | Build.IS_DEBUGGABLE;
+ DEBUG_BACKGROUND_BATTERY_TRACKER | false;
// As we don't support realtime per-UID battery usage stats yet, we're polling the stats
// in a regular time basis.
@@ -361,6 +361,7 @@
mUidBatteryUsageInWindow.removeAt(i);
}
}
+ mInjector.getPolicy().onUserRemovedLocked(userId);
}
}
@@ -369,6 +370,7 @@
synchronized (mLock) {
mUidBatteryUsage.delete(uid);
mUidBatteryUsageInWindow.delete(uid);
+ mInjector.getPolicy().onUidRemovedLocked(uid);
}
}
@@ -1209,6 +1211,14 @@
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window";
/**
+ * The grace period after an interaction event with the app, if the background current
+ * drain goes beyond the threshold within that period, the system won't apply the
+ * restrictions.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_interaction_grace_period";
+
+ /**
* Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher
* value for the legitimate cases with higher background current drain.
*/
@@ -1311,6 +1321,11 @@
final long mDefaultBgCurrentDrainWindowMs;
/**
+ * Default value to {@link #mBgCurrentDrainInteractionGracePeriodMs}.
+ */
+ final long mDefaultBgCurrentDrainInteractionGracePeriodMs;
+
+ /**
* Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
*/
@@ -1395,6 +1410,11 @@
volatile long mBgCurrentDrainWindowMs;
/**
+ * @see #KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD.
+ */
+ volatile long mBgCurrentDrainInteractionGracePeriodMs;
+
+ /**
* @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
*/
volatile long mBgCurrentDrainMediaPlaybackMinDuration;
@@ -1456,6 +1476,12 @@
private final SparseArray<Pair<long[], ImmutableBatteryUsage[]>> mHighBgBatteryPackages =
new SparseArray<>();
+ /**
+ * The timestamp of the last interaction, key is the UID.
+ */
+ @GuardedBy("mLock")
+ private final SparseLongArray mLastInteractionTime = new SparseLongArray();
+
@NonNull
private final Object mLock;
@@ -1479,6 +1505,7 @@
isLowRamDeviceStatic() ? val[1] : val[0];
mDefaultBgCurrentDrainWindowMs = resources.getInteger(
R.integer.config_bg_current_drain_window) * 1_000;
+ mDefaultBgCurrentDrainInteractionGracePeriodMs = mDefaultBgCurrentDrainWindowMs;
val = getFloatArray(resources.obtainTypedArray(
R.array.config_bg_current_drain_high_threshold_to_restricted_bucket));
mDefaultBgCurrentDrainRestrictedBucketHighThreshold =
@@ -1512,6 +1539,8 @@
mBgCurrentDrainBgRestrictedThreshold[1] =
mDefaultBgCurrentDrainBgRestrictedHighThreshold;
mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs;
+ mBgCurrentDrainInteractionGracePeriodMs =
+ mDefaultBgCurrentDrainInteractionGracePeriodMs;
mBgCurrentDrainMediaPlaybackMinDuration =
mDefaultBgCurrentDrainMediaPlaybackMinDuration;
mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration;
@@ -1543,6 +1572,9 @@
case KEY_BG_CURRENT_DRAIN_WINDOW:
updateCurrentDrainWindow();
break;
+ case KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD:
+ updateCurrentDrainInteractionGracePeriod();
+ break;
case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION:
updateCurrentDrainMediaPlaybackMinDuration();
break;
@@ -1627,6 +1659,13 @@
mDefaultBgCurrentDrainWindowMs);
}
+ private void updateCurrentDrainInteractionGracePeriod() {
+ mBgCurrentDrainInteractionGracePeriodMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+ mDefaultBgCurrentDrainInteractionGracePeriodMs);
+ }
+
private void updateCurrentDrainMediaPlaybackMinDuration() {
mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1669,6 +1708,7 @@
super.onSystemReady();
updateCurrentDrainThreshold();
updateCurrentDrainWindow();
+ updateCurrentDrainInteractionGracePeriod();
updateCurrentDrainMediaPlaybackMinDuration();
updateCurrentDrainLocationMinDuration();
updateCurrentDrainEventDurationBasedThresholdEnabled();
@@ -1686,8 +1726,10 @@
synchronized (mLock) {
final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
if (pair != null) {
+ final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
final long[] ts = pair.first;
- final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0
+ final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
+ > (lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs)
&& mTracker.mAppRestrictionController.isAutoRestrictAbusiveAppEnabled()
? RESTRICTION_LEVEL_RESTRICTED_BUCKET
: RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
@@ -1778,6 +1820,7 @@
// We're already in the background restricted level, nothing more we could do.
return;
}
+ final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
final long now = SystemClock.elapsedRealtime();
final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
mBgCurrentDrainWindowMs);
@@ -1789,13 +1832,17 @@
long[] ts = null;
ImmutableBatteryUsage[] usages = null;
if (rbPercentage >= rbThreshold) {
- // New findings to us, track it and let the controller know.
- ts = new long[TIME_STAMP_INDEX_LAST];
- ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
- usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
- usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
- mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
- notifyController = excessive = true;
+ if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
+ // New findings to us, track it and let the controller know.
+ ts = new long[TIME_STAMP_INDEX_LAST];
+ ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+ usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
+ usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+ mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
+ // It's beeen long enough since last interaction with this app.
+ notifyController = true;
+ }
+ excessive = true;
}
if (decoupleThresholds && brPercentage >= brThreshold) {
if (ts == null) {
@@ -1813,11 +1860,15 @@
final long[] ts = pair.first;
final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
if (rbPercentage >= rbThreshold) {
- if (lastRestrictBucketTs == 0) {
- ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
- pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+ if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
+ if (lastRestrictBucketTs == 0) {
+ ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+ pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+ }
+ // It's been long enough since last interaction with this app.
+ notifyController = true;
}
- notifyController = excessive = true;
+ excessive = true;
} else {
// It's actually back to normal, but we don't untrack it until
// explicit user interactions, because the restriction could be the cause
@@ -1834,7 +1885,7 @@
&& (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs));
if (notifyController) {
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
- pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+ pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = usage;
}
excessive = true;
} else {
@@ -1842,7 +1893,7 @@
// user consent to unrestrict it; or if it's in restricted bucket level,
// resetting this won't lift it from that level.
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
- pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = null;
+ pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null;
// Now need to notify the controller.
}
}
@@ -1903,6 +1954,7 @@
void onUserInteractionStarted(String packageName, int uid) {
boolean changed = false;
synchronized (mLock) {
+ mLastInteractionTime.put(uid, SystemClock.elapsedRealtime());
final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(
uid, packageName);
if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
@@ -1941,9 +1993,30 @@
@VisibleForTesting
void reset() {
mHighBgBatteryPackages.clear();
+ mLastInteractionTime.clear();
mTracker.reset();
}
+ @GuardedBy("mLock")
+ void onUserRemovedLocked(final @UserIdInt int userId) {
+ for (int i = mHighBgBatteryPackages.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mHighBgBatteryPackages.keyAt(i)) == userId) {
+ mHighBgBatteryPackages.removeAt(i);
+ }
+ }
+ for (int i = mLastInteractionTime.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mLastInteractionTime.keyAt(i)) == userId) {
+ mLastInteractionTime.removeAt(i);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onUidRemovedLocked(final int uid) {
+ mHighBgBatteryPackages.remove(uid);
+ mLastInteractionTime.delete(uid);
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
@@ -1977,6 +2050,10 @@
pw.print('=');
pw.println(mBgCurrentDrainWindowMs);
pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD);
+ pw.print('=');
+ pw.println(mBgCurrentDrainInteractionGracePeriodMs);
+ pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
pw.print('=');
pw.println(mBgCurrentDrainMediaPlaybackMinDuration);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index dd7fb84..d2e40c5 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -860,6 +860,21 @@
}
}
+ // Check that the receiver does *not* belong to any of the excluded packages
+ if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
+ if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
+ Slog.w(TAG, "Skipping delivery of excluded package "
+ + r.intent.toString()
+ + " to " + filter.receiverList.app
+ + " (pid=" + filter.receiverList.pid
+ + ", uid=" + filter.receiverList.uid + ")"
+ + " excludes package " + filter.packageName
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")");
+ skip = true;
+ }
+ }
+
// If the broadcast also requires an app op check that as well.
if (!skip && r.appOp != AppOpsManager.OP_NONE
&& mService.getAppOpsManager().noteOpNoThrow(r.appOp,
@@ -1721,6 +1736,19 @@
}
}
+ // Check that the receiver does *not* belong to any of the excluded packages
+ if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
+ if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
+ Slog.w(TAG, "Skipping delivery of excluded package "
+ + r.intent + " to "
+ + component.flattenToShortString()
+ + " excludes package " + component.getPackageName()
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")");
+ skip = true;
+ }
+ }
+
if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
for (int i = 0; i < r.requiredPermissions.length; i++) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 5343af2..19ffc173 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -75,6 +75,7 @@
final String resolvedType; // the resolved data type
final String[] requiredPermissions; // permissions the caller has required
final String[] excludedPermissions; // permissions to exclude
+ final String[] excludedPackages; // packages to exclude
final int appOp; // an app op that is associated with this broadcast
final BroadcastOptions options; // BroadcastOptions supplied by caller
final List receivers; // contains BroadcastFilter and ResolveInfo
@@ -162,6 +163,10 @@
pw.print(prefix); pw.print("excludedPermissions=");
pw.print(Arrays.toString(excludedPermissions));
}
+ if (excludedPackages != null && excludedPackages.length > 0) {
+ pw.print(prefix); pw.print("excludedPackages=");
+ pw.print(Arrays.toString(excludedPackages));
+ }
if (options != null) {
pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
}
@@ -260,7 +265,8 @@
Intent _intent, ProcessRecord _callerApp, String _callerPackage,
@Nullable String _callerFeatureId, int _callingPid, int _callingUid,
boolean _callerInstantApp, String _resolvedType,
- String[] _requiredPermissions, String[] _excludedPermissions, int _appOp,
+ String[] _requiredPermissions, String[] _excludedPermissions,
+ String[] _excludedPackages, int _appOp,
BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
@@ -280,6 +286,7 @@
resolvedType = _resolvedType;
requiredPermissions = _requiredPermissions;
excludedPermissions = _excludedPermissions;
+ excludedPackages = _excludedPackages;
appOp = _appOp;
options = _options;
receivers = _receivers;
@@ -321,6 +328,7 @@
resolvedType = from.resolvedType;
requiredPermissions = from.requiredPermissions;
excludedPermissions = from.excludedPermissions;
+ excludedPackages = from.excludedPackages;
appOp = from.appOp;
options = from.options;
receivers = from.receivers;
@@ -381,9 +389,10 @@
// build a new BroadcastRecord around that single-target list
BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, appOp, options, splitReceivers, resultTo,
- resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
- allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
+ splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky,
+ initialSticky, userId, allowBackgroundActivityStarts,
+ mBackgroundActivityStartsToken, timeoutExempt);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
@@ -459,7 +468,7 @@
for (int i = 0; i < uidSize; i++) {
final BroadcastRecord br = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, appOp, options,
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
uid2receiverList.valueAt(i), null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index 2db3b15..01735a7 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -483,7 +483,7 @@
@Nullable
public Resolution<ResolveInfo> resolveReceiver(@NonNull Intent intent,
@NonNull ResolveInfo receiver, @Nullable String resolvedType,
- int packageFlags, int userId, int callingUid) {
+ int packageFlags, int userId, int callingUid, boolean forSend) {
// Resolve this alias.
final Resolution<ComponentName> resolution = resolveComponentAlias(() ->
receiver.activityInfo.getComponentName());
@@ -506,8 +506,8 @@
i.setPackage(null);
i.setComponent(resolution.getTarget());
- List<ResolveInfo> resolved = pmi.queryIntentReceivers(i,
- resolvedType, packageFlags, callingUid, userId);
+ List<ResolveInfo> resolved = pmi.queryIntentReceivers(
+ i, resolvedType, packageFlags, callingUid, userId, forSend);
if (resolved == null || resolved.size() == 0) {
// Target component not found.
Slog.w(TAG, "Alias target " + target.flattenToShortString() + " not found");
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 18fb6a4..08a6719 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -24,9 +24,14 @@
/** Rate limiter for adding errors into dropbox. */
public class DropboxRateLimiter {
- private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
- private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
- private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;
+ // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of
+ // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has
+ // elapsed, after which the current count for this breakdown will be reset.
+ private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS;
+ // The time duration after which the rate limit buffer will be cleared.
+ private static final long RATE_LIMIT_BUFFER_EXPIRY = 3 * RATE_LIMIT_BUFFER_DURATION;
+ // The number of entries to keep per breakdown of process/eventType.
+ private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6;
@GuardedBy("mErrorClusterRecords")
private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 7562098..35f91ba 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -124,7 +124,7 @@
REASON_PRE_BOOT_COMPLETED, "");
synchronized (mService) {
mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null,
- null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
+ null, null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
false, ActivityManagerService.MY_PID,
Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fc73a59..a2048a3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2028,8 +2028,10 @@
mService.mProcessList.handlePredecessorProcDied((ProcessRecord) msg.obj);
break;
case MSG_PROCESS_KILL_TIMEOUT:
- mService.handleProcessStartOrKillTimeoutLocked((ProcessRecord) msg.obj,
- /* isKillTimeout */ true);
+ synchronized (mService) {
+ mService.handleProcessStartOrKillTimeoutLocked((ProcessRecord) msg.obj,
+ /* isKillTimeout */ true);
+ }
break;
}
}
@@ -2792,6 +2794,15 @@
}
int N = procs.size();
+ for (int i = 0; i < N; ++i) {
+ final ProcessRecord proc = procs.get(i).first;
+ try {
+ Process.setProcessFrozen(proc.getPid(), proc.uid, true);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to freeze " + proc.getPid() + " " + proc.processName);
+ }
+ }
+
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c043773..7ffea26 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3203,8 +3203,8 @@
synchronized (mService) {
return mService.broadcastIntentLocked(null, null, null, intent, resolvedType,
resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
- appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
- realCallingPid, userId);
+ null, appOp, bOptions, ordered, sticky, callingPid, callingUid,
+ realCallingUid, realCallingPid, userId);
}
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index d239c02..27ce493 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -501,6 +501,7 @@
null /* resultExtras */,
requiredPermissions,
null /* excludedPermissions */,
+ null /* excludedPackages */,
OP_NONE,
null /* bOptions */,
false /* serialized */,
@@ -519,6 +520,7 @@
null /* resultExtras */,
requiredPermissions,
null /* excludedPermissions */,
+ null /* excludedPackages */,
OP_NONE,
null /* bOptions */,
false /* serialized */,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 3e97b91..b9da144 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -43,6 +43,7 @@
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
@@ -2368,7 +2369,8 @@
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
if (!isSelfRequest) {
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
boolean isCallerPermissionController;
try {
@@ -3848,7 +3850,7 @@
// the data gated by OP_RECORD_AUDIO.
//
// TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD) {
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
@@ -4545,8 +4547,9 @@
* @return The restriction matching the package
*/
private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
- return new RestrictionBypass(pkg.isPrivileged(), mContext.checkPermission(
- android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+ mContext.checkPermission(android.Manifest.permission
+ .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
== PackageManager.PERMISSION_GRANTED);
}
@@ -4853,6 +4856,9 @@
if (opBypass != null) {
// If we are the system, bypass user restrictions for certain codes
synchronized (this) {
+ if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+ return false;
+ }
if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
return false;
}
@@ -6920,7 +6926,8 @@
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
if (!isCallerSystem && !isCallerInstrumented) {
return null;
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 8de515d..158092f 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -34,6 +34,7 @@
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.flagsToString;
import static android.app.AppOpsManager.getUidStateName;
@@ -133,7 +134,7 @@
private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+ "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
- + OP_PHONE_CALL_CAMERA;
+ + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 3b715a2..03dcc8d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1289,8 +1289,11 @@
break;
case MSG_L_SET_BT_ACTIVE_DEVICE:
synchronized (mDeviceStateLock) {
- mDeviceInventory.onSetBtActiveDevice((BtDeviceInfo) msg.obj,
- mAudioService.getBluetoothContextualVolumeStream());
+ BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ mDeviceInventory.onSetBtActiveDevice(btInfo,
+ (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
}
break;
case MSG_BT_HEADSET_CNCT_FAILED:
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index aed63ce..8c0e2dd 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4146,19 +4146,8 @@
{
streamType = mStreamVolumeAlias[streamType];
- if (streamType == AudioSystem.STREAM_MUSIC) {
- flags = updateFlagsForTvPlatform(flags);
- synchronized (mHdmiClientLock) {
- // Don't display volume UI on a TV Playback device when using absolute volume
- if (mHdmiCecVolumeControlEnabled && mHdmiPlaybackClient != null
- && (isAbsoluteVolumeDevice(device)
- || isA2dpAbsoluteVolumeDevice(device))) {
- flags &= ~AudioManager.FLAG_SHOW_UI;
- }
- }
- if (isFullVolumeDevice(device)) {
- flags &= ~AudioManager.FLAG_SHOW_UI;
- }
+ if (streamType == AudioSystem.STREAM_MUSIC && isFullVolumeDevice(device)) {
+ flags &= ~AudioManager.FLAG_SHOW_UI;
}
mVolumeController.postVolumeChanged(streamType, flags);
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 122a950..0b9cb19 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -70,12 +70,6 @@
private @Nullable SensorManager mSensorManager;
//------------------------------------------------------------
- /** head tracker sensor name */
- // TODO: replace with generic head tracker sensor name.
- // the current implementation refers to the "google" namespace but will be replaced
- // by an android name at the next API level revision, it is not Google-specific.
- private static final String HEADTRACKER_SENSOR =
- "com.google.hardware.sensor.hid_dynamic.headtracker";
private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) {
{
@@ -125,6 +119,8 @@
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private boolean mTransauralSupported = false;
private boolean mBinauralSupported = false;
+ private boolean mIsHeadTrackingSupported = false;
+ private int[] mSupportedHeadTrackingModes = new int[0];
private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
private boolean mHeadTrackerAvailable = false;
@@ -137,9 +133,9 @@
private int mSpatOutput = 0;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
- private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
+ private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback =
+ new SpatializerHeadTrackingCallback();
private @Nullable HelperDynamicSensorCallback mDynSensorCallback;
- private boolean mIsHeadTrackingSupported = false;
// default attributes and format that determine basic availability of spatialization
private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
@@ -195,6 +191,8 @@
return;
}
// capabilities of spatializer?
+ resetCapabilities();
+
try {
byte[] levels = spat.getSupportedLevels();
if (levels == null
@@ -213,6 +211,38 @@
break;
}
}
+
+ // Note: head tracking support must be initialized before spatialization modes as
+ // addCompatibleAudioDevice() calls onRoutingUpdated() which will initialize the
+ // sensors according to mIsHeadTrackingSupported.
+ mIsHeadTrackingSupported = spat.isHeadTrackingSupported();
+ if (mIsHeadTrackingSupported) {
+ final byte[] values = spat.getSupportedHeadTrackingModes();
+ ArrayList<Integer> list = new ArrayList<>(0);
+ for (byte value : values) {
+ switch (value) {
+ case SpatializerHeadTrackingMode.OTHER:
+ case SpatializerHeadTrackingMode.DISABLED:
+ // not expected here, skip
+ break;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ list.add(headTrackingModeTypeToSpatializerInt(value));
+ break;
+ default:
+ Log.e(TAG, "Unexpected head tracking mode:" + value,
+ new IllegalArgumentException("invalid mode"));
+ break;
+ }
+ }
+ mSupportedHeadTrackingModes = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ mSupportedHeadTrackingModes[i] = list.get(i);
+ }
+ mActualHeadTrackingMode =
+ headTrackingModeTypeToSpatializerInt(spat.getActualHeadTrackingMode());
+ }
+
byte[] spatModes = spat.getSupportedModes();
for (byte mode : spatModes) {
switch (mode) {
@@ -258,7 +288,7 @@
}
// TODO read persisted states
} catch (RemoteException e) {
- /* capable level remains at NONE*/
+ resetCapabilities();
} finally {
if (spat != null) {
try {
@@ -285,12 +315,19 @@
releaseSpat();
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
init(true);
setSpatializerEnabledInt(featureEnabled);
}
+ private void resetCapabilities() {
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mBinauralSupported = false;
+ mTransauralSupported = false;
+ mIsHeadTrackingSupported = false;
+ mSupportedHeadTrackingModes = new int[0];
+ }
+
//------------------------------------------------------
// routing monitoring
synchronized void onRoutingUpdated() {
@@ -486,8 +523,7 @@
for (SADeviceState deviceState : mSADevices) {
if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
isInList = true;
if (forceEnable) {
deviceState.mEnabled = true;
@@ -511,8 +547,7 @@
for (SADeviceState deviceState : mSADevices) {
if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
deviceState.mEnabled = false;
break;
}
@@ -854,18 +889,19 @@
private void createSpat() {
if (mSpat == null) {
mSpatCallback = new SpatializerCallback();
- mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
mSpat = AudioSystem.getSpatializer(mSpatCallback);
try {
- mIsHeadTrackingSupported = mSpat.isHeadTrackingSupported();
//TODO: register heatracking callback only when sensors are registered
if (mIsHeadTrackingSupported) {
+ mActualHeadTrackingMode =
+ headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode());
mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "Can't configure head tracking", e);
mState = STATE_NOT_SUPPORTED;
mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
}
}
}
@@ -885,7 +921,6 @@
} catch (RemoteException e) {
Log.e(TAG, "Can't set release spatializer cleanly", e);
}
- mIsHeadTrackingSupported = false;
mSpat = null;
}
}
@@ -953,76 +988,11 @@
}
synchronized int[] getSupportedHeadTrackingModes() {
- switch (mState) {
- case STATE_UNINITIALIZED:
- return new int[0];
- case STATE_NOT_SUPPORTED:
- // return an empty list when Spatializer functionality is not supported
- // because the list of head tracking modes you can set is actually empty
- // as defined in {@link Spatializer#getSupportedHeadTrackingModes()}
- return new int[0];
- case STATE_ENABLED_UNAVAILABLE:
- case STATE_DISABLED_UNAVAILABLE:
- case STATE_DISABLED_AVAILABLE:
- case STATE_ENABLED_AVAILABLE:
- if (mSpat == null) {
- return new int[0];
- }
- break;
- }
- // mSpat != null
- try {
- final byte[] values = mSpat.getSupportedHeadTrackingModes();
- ArrayList<Integer> list = new ArrayList<>(0);
- for (byte value : values) {
- switch (value) {
- case SpatializerHeadTrackingMode.OTHER:
- case SpatializerHeadTrackingMode.DISABLED:
- // not expected here, skip
- break;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
- list.add(headTrackingModeTypeToSpatializerInt(value));
- break;
- default:
- Log.e(TAG, "Unexpected head tracking mode:" + value,
- new IllegalArgumentException("invalid mode"));
- break;
- }
- }
- int[] modes = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- modes[i] = list.get(i);
- }
- return modes;
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e);
- return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED };
- }
+ return mSupportedHeadTrackingModes;
}
synchronized int getActualHeadTrackingMode() {
- switch (mState) {
- case STATE_UNINITIALIZED:
- return Spatializer.HEAD_TRACKING_MODE_DISABLED;
- case STATE_NOT_SUPPORTED:
- return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- case STATE_ENABLED_UNAVAILABLE:
- case STATE_DISABLED_UNAVAILABLE:
- case STATE_DISABLED_AVAILABLE:
- case STATE_ENABLED_AVAILABLE:
- if (mSpat == null) {
- return Spatializer.HEAD_TRACKING_MODE_DISABLED;
- }
- break;
- }
- // mSpat != null
- try {
- return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
- return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- }
+ return mActualHeadTrackingMode;
}
synchronized int getDesiredHeadTrackingMode() {
@@ -1467,19 +1437,19 @@
pw.println("\tmState:" + mState);
pw.println("\tmSpatLevel:" + mSpatLevel);
pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel);
- pw.println("\tmActualHeadTrackingMode:"
- + Spatializer.headtrackingModeToString(mActualHeadTrackingMode));
- pw.println("\tmDesiredHeadTrackingMode:"
- + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode));
- pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
- + mTransauralSupported);
+ pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported);
StringBuilder modesString = new StringBuilder();
- int[] modes = getSupportedHeadTrackingModes();
- for (int mode : modes) {
+ for (int mode : mSupportedHeadTrackingModes) {
modesString.append(Spatializer.headtrackingModeToString(mode)).append(" ");
}
pw.println("\tsupported head tracking modes:" + modesString);
+ pw.println("\tmDesiredHeadTrackingMode:"
+ + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode));
+ pw.println("\tmActualHeadTrackingMode:"
+ + Spatializer.headtrackingModeToString(mActualHeadTrackingMode));
pw.println("\theadtracker available:" + mHeadTrackerAvailable);
+ pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
+ + mTransauralSupported);
pw.println("\tmSpatOutput:" + mSpatOutput);
pw.println("\tdevices:");
for (SADeviceState device : mSADevices) {
@@ -1546,24 +1516,24 @@
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
- List<Sensor> sensors = new ArrayList<Sensor>(0);
- sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER));
- sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE));
+ // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
+ // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
+ // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
+ // SensorPoseProvider).
+ // Note: this is a dynamic sensor list right now.
+ List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER);
for (Sensor sensor : sensors) {
- if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER
- || sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
- UUID uuid = sensor.getUuid();
- if (uuid.equals(routingDeviceUuid)) {
- headHandle = sensor.getHandle();
- if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
- headHandle = -1;
- }
- break;
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ headHandle = sensor.getHandle();
+ if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
+ headHandle = -1;
}
- if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
- headHandle = sensor.getHandle();
- break;
- }
+ break;
+ }
+ if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ headHandle = sensor.getHandle();
+ // we do not break, perhaps we find a head tracker on device.
}
}
return headHandle;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index de0a36a..bf7a62a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -88,6 +88,10 @@
mCallback.onClientFinished(this, true /* success */);
}
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
+
/**
* Reset the local lockout state and notify any listeners.
*
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 6e74d36..f29b9e8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -65,6 +65,10 @@
startHalOperation();
}
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
+
@Override
protected void startHalOperation() {
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index f90cba7..c8148df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -82,6 +82,10 @@
}
}
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
+
void onLockoutCleared() {
resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
mLockoutResetDispatcher);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index 559ca06..843fcc8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -50,6 +50,10 @@
callback.onClientFinished(this, true /* success */);
}
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
+
@Override
public int getProtoEnum() {
return BiometricsProto.CM_RESET_LOCKOUT;
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index 28c7cad..c37d4c6 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.content.ClipData;
+import android.os.PersistableBundle;
import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
@@ -25,6 +26,7 @@
import android.system.VmSocketAddress;
import android.util.Slog;
+import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.SocketException;
@@ -93,16 +95,16 @@
}
}
- private byte[] receiveMessage() throws ErrnoException, InterruptedIOException {
+ private byte[] receiveMessage() throws ErrnoException, InterruptedIOException, EOFException {
final byte[] lengthBits = new byte[4];
- Os.read(mPipe, lengthBits, 0, lengthBits.length);
+ readFully(mPipe, lengthBits, 0, lengthBits.length);
final ByteBuffer bb = ByteBuffer.wrap(lengthBits);
bb.order(ByteOrder.LITTLE_ENDIAN);
final int msgLen = bb.getInt();
final byte[] msg = new byte[msgLen];
- Os.read(mPipe, msg, 0, msg.length);
+ readFully(mPipe, msg, 0, msg.length);
return msg;
}
@@ -115,8 +117,8 @@
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(msg.length);
- Os.write(fd, lengthBits, 0, lengthBits.length);
- Os.write(fd, msg, 0, msg.length);
+ writeFully(fd, lengthBits, 0, lengthBits.length);
+ writeFully(fd, msg, 0, msg.length);
}
EmulatorClipboardMonitor(final Consumer<ClipData> setAndroidClipboard) {
@@ -136,12 +138,15 @@
final ClipData clip = new ClipData("host clipboard",
new String[]{"text/plain"},
new ClipData.Item(str));
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean("com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY", true);
+ clip.getDescription().setExtras(bundle);
if (LOG_CLIBOARD_ACCESS) {
Slog.i(TAG, "Setting the guest clipboard to '" + str + "'");
}
setAndroidClipboard.accept(clip);
- } catch (ErrnoException | InterruptedIOException e) {
+ } catch (ErrnoException | EOFException | InterruptedIOException e) {
closePipe();
} catch (InterruptedException | IllegalArgumentException e) {
}
@@ -182,4 +187,32 @@
t.start();
}
}
+
+ private static void readFully(final FileDescriptor fd,
+ final byte[] buf, int offset, int size)
+ throws ErrnoException, InterruptedIOException, EOFException {
+ while (size > 0) {
+ final int r = Os.read(fd, buf, offset, size);
+ if (r > 0) {
+ offset += r;
+ size -= r;
+ } else {
+ throw new EOFException();
+ }
+ }
+ }
+
+ private static void writeFully(final FileDescriptor fd,
+ final byte[] buf, int offset, int size)
+ throws ErrnoException, InterruptedIOException {
+ while (size > 0) {
+ final int r = Os.write(fd, buf, offset, size);
+ if (r > 0) {
+ offset += r;
+ size -= r;
+ } else {
+ throw new ErrnoException("write", OsConstants.EIO);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9824b4e..f8a74f4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4176,7 +4176,11 @@
List<AudioDeviceAttributes> streamMusicDevices =
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
- setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
+ int flags = AudioManager.FLAG_ABSOLUTE_VOLUME;
+ if (isTvDevice()) {
+ flags |= AudioManager.FLAG_SHOW_UI;
+ }
+ setStreamMusicVolume(volume, flags);
}
}
@@ -4190,8 +4194,11 @@
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
- getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
- AudioManager.FLAG_ABSOLUTE_VOLUME);
+ int flags = AudioManager.FLAG_ABSOLUTE_VOLUME;
+ if (isTvDevice()) {
+ flags |= AudioManager.FLAG_SHOW_UI;
+ }
+ getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, flags);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bf267a2..b624d43 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -164,6 +165,7 @@
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+ private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
@@ -276,11 +278,24 @@
@GuardedBy("mAssociationLock")
private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+ // Guards per-display input properties and properties relating to the mouse pointer.
+ // Threads can wait on this lock to be notified the next time the display on which the mouse
+ // pointer is shown has changed.
private final Object mAdditionalDisplayInputPropertiesLock = new Object();
- // Forces the MouseCursorController to target a specific display id.
+ // Forces the PointerController to target a specific display id.
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+ // PointerController is the source of truth of the pointer display. This is the value of the
+ // latest pointer display id reported by PointerController.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
+ // This is the latest display id that IMS has requested PointerController to use. If there are
+ // no devices that can control the pointer, PointerController may end up disregarding this
+ // value.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
new SparseArray<>();
@@ -289,7 +304,6 @@
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private PointerIcon mIcon;
-
// Holds all the registered gesture monitors that are implemented as spy windows. The spy
// windows are mapped by their InputChannel tokens.
@GuardedBy("mInputMonitors")
@@ -383,6 +397,10 @@
NativeInputManagerService getNativeService(InputManagerService service) {
return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
}
+
+ void registerLocalService(InputManagerInternal localService) {
+ LocalServices.addService(InputManagerInternal.class, localService);
+ }
}
public InputManagerService(Context context) {
@@ -409,7 +427,7 @@
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
- LocalServices.addService(InputManagerInternal.class, new LocalService());
+ injector.registerLocalService(new LocalService());
}
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
@@ -559,6 +577,8 @@
vArray[i] = viewports.get(i);
}
mNative.setDisplayViewports(vArray);
+ // Always attempt to update the pointer display when viewports change.
+ updatePointerDisplayId();
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
final AdditionalDisplayInputProperties properties =
@@ -841,6 +861,19 @@
@Override // Binder call
public boolean injectInputEvent(InputEvent event, int mode) {
+ return injectInputEventToTarget(event, mode, Process.INVALID_UID);
+ }
+
+ @Override // Binder call
+ public boolean injectInputEventToTarget(InputEvent event, int mode, int targetUid) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "injectInputEvent()", true /*checkInstrumentationSource*/)) {
+ throw new SecurityException(
+ "Injecting input events requires the caller (or the source of the "
+ + "instrumentation, if any) to have the INJECT_EVENTS permission.");
+ }
+ // We are not checking if targetUid matches the callingUid, since having the permission
+ // already means you can inject into any window.
Objects.requireNonNull(event, "event must not be null");
if (mode != InputEventInjectionSync.NONE
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -849,22 +882,39 @@
}
final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
+ final boolean injectIntoUid = targetUid != Process.INVALID_UID;
final int result;
try {
- result = mNative.injectInputEvent(event, pid, uid, mode,
- INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ result = mNative.injectInputEvent(event, injectIntoUid,
+ targetUid, mode, INJECTION_TIMEOUT_MILLIS,
+ WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
} finally {
Binder.restoreCallingIdentity(ident);
}
switch (result) {
- case InputEventInjectionResult.PERMISSION_DENIED:
- Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
- throw new SecurityException(
- "Injecting to another application requires INJECT_EVENTS permission");
case InputEventInjectionResult.SUCCEEDED:
return true;
+ case InputEventInjectionResult.TARGET_MISMATCH:
+ if (!injectIntoUid) {
+ throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
+ + " when it is not targeted into to a specific uid.");
+ }
+ // TODO(b/228161340): Remove the fallback of targeting injection into all windows
+ // when the caller has the injection permission.
+ // Explicitly maintain the same behavior as previous versions of Android, where
+ // injection is allowed into all windows if the caller has the INJECT_EVENTS
+ // permission, even if it is targeting a certain uid.
+ if (checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "injectInputEvent-target-mismatch-fallback")) {
+ Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
+ + targetUid + ". Falling back to injecting into all windows.");
+ return injectInputEventToTarget(event, mode, Process.INVALID_UID);
+ }
+ throw new IllegalArgumentException(
+ "Targeted input event injection from pid " + pid
+ + " was not directed at a window owned by uid "
+ + targetUid + ".");
case InputEventInjectionResult.TIMED_OUT:
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
return false;
@@ -1693,6 +1743,13 @@
mPointerIconDisplayContext = null;
}
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ setPointerIconVisible(AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE,
+ displayId);
+ setPointerAcceleration(AdditionalDisplayInputProperties.DEFAULT_POINTER_ACCELERATION,
+ displayId);
+ }
+
mNative.displayRemoved(displayId);
}
@@ -1964,10 +2021,43 @@
return result;
}
- private void setVirtualMousePointerDisplayId(int displayId) {
+ /**
+ * Update the display on which the mouse pointer is shown.
+ * If there is an overridden display for the mouse pointer, use that. Otherwise, query
+ * WindowManager for the pointer display.
+ *
+ * @return true if the pointer displayId changed, false otherwise.
+ */
+ private boolean updatePointerDisplayId() {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY
+ ? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId();
+ if (mRequestedPointerDisplayId == pointerDisplayId) {
+ return false;
+ }
+ mRequestedPointerDisplayId = pointerDisplayId;
+ mNative.setPointerDisplayId(pointerDisplayId);
+ return true;
+ }
+ }
+
+ private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
+ // Notify waiting threads that the display of the mouse pointer has changed.
+ mAdditionalDisplayInputPropertiesLock.notifyAll();
+ }
+ mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
+ args.mPointerDisplayId, args.mXPosition, args.mYPosition);
+ }
+
+ private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) {
+ // Indicates whether this request is for removing the override.
+ final boolean removingOverride = displayId == Display.INVALID_DISPLAY;
+
synchronized (mAdditionalDisplayInputPropertiesLock) {
mOverriddenPointerDisplayId = displayId;
- if (displayId != Display.INVALID_DISPLAY) {
+ if (!removingOverride) {
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.get(displayId);
if (properties != null) {
@@ -1975,9 +2065,30 @@
updatePointerIconVisibleLocked(properties.pointerIconVisible);
}
}
+ if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) {
+ // The requested pointer display is already set.
+ return true;
+ }
+ if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
+ // The pointer display override is being removed, but the current pointer display
+ // is already invalid. This can happen when the PointerController is destroyed as a
+ // result of the removal of all input devices that can control the pointer.
+ return true;
+ }
+ try {
+ // The pointer display changed, so wait until the change has propagated.
+ mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
+ } catch (InterruptedException ignored) {
+ }
+ // This request succeeds in two cases:
+ // - This request was to remove the override, in which case the new pointer display
+ // could be anything that WM has set.
+ // - We are setting a new override, in which case the request only succeeds if the
+ // reported new displayId is the one we requested. This check ensures that if two
+ // competing overrides are requested in succession, the caller can be notified if one
+ // of them fails.
+ return removingOverride || mAcknowledgedPointerDisplayId == displayId;
}
- // TODO(b/215597605): trigger MousePositionTracker update
- mNative.notifyPointerDisplayIdChanged();
}
private int getVirtualMousePointerDisplayId() {
@@ -2700,8 +2811,12 @@
}
}
}
-
private boolean checkCallingPermission(String permission, String func) {
+ return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
+ }
+
+ private boolean checkCallingPermission(String permission, String func,
+ boolean checkInstrumentationSource) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -2710,6 +2825,28 @@
if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
return true;
}
+
+ if (checkInstrumentationSource) {
+ final ActivityManagerInternal ami =
+ LocalServices.getService(ActivityManagerInternal.class);
+ Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+ final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+ if (instrumentationUid != Process.INVALID_UID) {
+ // Clear the calling identity when checking if the instrumentation source has
+ // permission because PackageManager will deny all permissions to some callers,
+ // such as instant apps.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mContext.checkPermission(permission, -1 /*pid*/, instrumentationUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
@@ -2955,13 +3092,6 @@
// Native callback.
@SuppressWarnings("unused")
- private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
- return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
- injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void onPointerDownOutsideFocus(IBinder touchedToken) {
mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
}
@@ -3159,18 +3289,6 @@
// Native callback.
@SuppressWarnings("unused")
- private int getPointerDisplayId() {
- synchronized (mAdditionalDisplayInputPropertiesLock) {
- // Prefer the override to all other displays.
- if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
- return mOverriddenPointerDisplayId;
- }
- }
- return mWindowManagerCallbacks.getPointerDisplayId();
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
if (!mSystemReady) {
return null;
@@ -3209,6 +3327,26 @@
return null;
}
+ private static class PointerDisplayIdChangedArgs {
+ final int mPointerDisplayId;
+ final float mXPosition;
+ final float mYPosition;
+ PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
+ mPointerDisplayId = pointerDisplayId;
+ mXPosition = xPosition;
+ mYPosition = yPosition;
+ }
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
+ @VisibleForTesting
+ void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
+ mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
+ new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
+ yPosition)).sendToTarget();
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -3332,6 +3470,14 @@
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
+
+ /**
+ * Notify WindowManagerService when the display of the mouse pointer changes.
+ * @param displayId The display on which the mouse pointer is shown.
+ * @param x The x coordinate of the mouse pointer.
+ * @param y The y coordinate of the mouse pointer.
+ */
+ void notifyPointerDisplayIdChanged(int displayId, float x, float y);
}
/**
@@ -3384,6 +3530,9 @@
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_POINTER_DISPLAY_ID_CHANGED:
+ handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
+ break;
}
}
}
@@ -3402,12 +3551,17 @@
@Override
public void sendInputEvent(InputEvent event, int policyFlags) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "sendInputEvent()")) {
+ throw new SecurityException(
+ "The INJECT_EVENTS permission is required for injecting input events.");
+ }
Objects.requireNonNull(event, "event must not be null");
synchronized (mInputFilterLock) {
if (!mDisconnected) {
- mNative.injectInputEvent(event, 0, 0,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+ mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
}
}
@@ -3634,8 +3788,9 @@
}
@Override
- public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
- InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
+ public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
+ return InputManagerService.this
+ .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
}
@Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 2169155..9cf8073 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -70,7 +70,18 @@
void setBlockUntrustedTouchesMode(int mode);
- int injectInputEvent(InputEvent event, int pid, int uid, int syncMode,
+ /**
+ * Inject an input event into the system.
+ *
+ * @param event the input event to inject
+ * @param injectIntoUid true if the event should target windows owned by uid, false otherwise
+ * @param uid the uid whose windows should be targeted, if any
+ * @param syncMode {@link android.os.InputEventInjectionSync}
+ * @param timeoutMillis timeout to wait for input injection to complete, in milliseconds
+ * @param policyFlags defined in {@link android.view.WindowManagerPolicyConstants}
+ * @return {@link android.os.InputEventInjectionResult}
+ */
+ int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode,
int timeoutMillis, int policyFlags);
VerifiedInputEvent verifyInputEvent(InputEvent event);
@@ -176,6 +187,9 @@
void cancelCurrentTouch();
+ /** Set the displayId on which the mouse cursor should be shown. */
+ void setPointerDisplayId(int displayId);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -237,7 +251,8 @@
public native void setBlockUntrustedTouchesMode(int mode);
@Override
- public native int injectInputEvent(InputEvent event, int pid, int uid, int syncMode,
+ public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid,
+ int syncMode,
int timeoutMillis, int policyFlags);
@Override
@@ -388,5 +403,8 @@
@Override
public native void cancelCurrentTouch();
+
+ @Override
+ public native void setPointerDisplayId(int displayId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index db17c105..8180e66 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -64,7 +64,9 @@
| InputConfig.INTERCEPTS_STYLUS
| InputConfig.TRUSTED_OVERLAY;
- // The touchable region of this input surface is not initially configured.
+ // Configure the surface to receive stylus events across the entire display.
+ mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
@@ -81,10 +83,6 @@
mWindowHandle.ownerUid = imeUid;
mWindowHandle.inputConfig &= ~InputConfig.SPY;
- // Update the touchable region so that the IME can intercept stylus events
- // across the entire display.
- mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
-
new SurfaceControl.Transaction()
.setInputWindowInfo(mInputSurface, mWindowHandle)
.apply();
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index a706772..f89b6ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -91,7 +91,7 @@
* InputEventReceiver that batches events according to the current thread's Choreographer.
*/
@UiThread
- void initializeHandwritingSpy(int displayId, IBinder focusedWindowToken) {
+ void initializeHandwritingSpy(int displayId) {
// When resetting, reuse resources if we are reinitializing on the same display.
reset(displayId == mCurrentDisplayId);
mCurrentDisplayId = displayId;
@@ -115,12 +115,6 @@
mHandwritingSurface = new HandwritingEventReceiverSurface(
name, displayId, surface, channel);
- // Configure the handwriting window to receive events over the focused window's bounds.
- mWindowManagerInternal.replaceInputSurfaceTouchableRegionWithWindowCrop(
- mHandwritingSurface.getSurface(),
- mHandwritingSurface.getInputWindowHandle(),
- focusedWindowToken);
-
// Use a dup of the input channel so that event processing can be paused by disposing the
// event receiver without causing a fd hangup.
mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
@@ -149,7 +143,8 @@
*/
@UiThread
@Nullable
- HandwritingSession startHandwritingSession(int requestId, int imePid, int imeUid) {
+ HandwritingSession startHandwritingSession(
+ int requestId, int imePid, int imeUid, IBinder focusedWindowToken) {
if (mHandwritingSurface == null) {
Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
return null;
@@ -158,12 +153,20 @@
Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
return null;
}
- if (!mRecordingGesture) {
+ if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) {
Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded.");
return null;
}
Objects.requireNonNull(mHandwritingEventReceiver,
"Handwriting session was already transferred to IME.");
+ final MotionEvent downEvent = mHandwritingBuffer.get(0);
+ assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN);
+ if (!mWindowManagerInternal.isPointInsideWindow(
+ focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) {
+ Slog.e(TAG, "Cannot start handwriting session: "
+ + "Stylus gesture did not start inside the focused window.");
+ return null;
+ }
if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
@@ -226,13 +229,17 @@
}
if (!(ev instanceof MotionEvent)) {
- Slog.e("Stylus", "Received non-motion event in stylus monitor.");
+ Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
return false;
}
final MotionEvent event = (MotionEvent) ev;
if (!isStylusEvent(event)) {
return false;
}
+ if (event.getDisplayId() != mCurrentDisplayId) {
+ Slog.wtf(TAG, "Received stylus event associated with the incorrect display.");
+ return false;
+ }
onStylusEvent(event);
return true;
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index e62c5c1..1703310 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -30,6 +30,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -148,10 +149,11 @@
@AnyThread
void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
- boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) {
+ boolean restarting, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
try {
mTarget.startInput(startInputToken, inputContext, attribute, restarting,
- navButtonFlags);
+ navButtonFlags, imeDispatcher);
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6af00b3..ea2b157 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -150,6 +150,7 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -616,6 +617,12 @@
IInputContext mCurInputContext;
/**
+ * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+ * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+ */
+ ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+ /**
* The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
*/
@Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
@@ -2623,7 +2630,7 @@
final SessionState session = mCurClient.curSession;
setEnabledSessionLocked(session);
session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
- navButtonFlags);
+ navButtonFlags, mCurImeDispatcher);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2733,7 +2740,8 @@
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
@NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
@StartInputReason int startInputReason,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
// If no method is currently selected, do nothing.
final String selectedMethodId = getSelectedMethodIdLocked();
if (selectedMethodId == null) {
@@ -2777,6 +2785,7 @@
mCurClient = cs;
mCurInputContext = inputContext;
mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
+ mCurImeDispatcher = imeDispatcher;
mCurVirtualDisplayToScreenMatrix =
getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId,
mDisplayIdToShowIme);
@@ -3780,10 +3789,12 @@
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, attribute, inputContext,
- remoteAccessibilityInputConnection, unverifiedTargetSdkVersion);
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion,
+ imeDispatcher);
}
@NonNull
@@ -3792,7 +3803,8 @@
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (windowToken == null) {
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
@@ -3829,7 +3841,7 @@
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
attribute, inputContext, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId);
+ unverifiedTargetSdkVersion, userId, imeDispatcher);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3857,7 +3869,8 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
IInputContext inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId) {
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3868,7 +3881,8 @@
+ InputMethodDebug.startInputFlagsToString(startInputFlags)
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
- + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
+ + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ + " imeDispatcher=" + imeDispatcher);
}
final ClientState cs = mClients.get(client.asBinder());
@@ -3952,7 +3966,7 @@
if (attribute != null) {
return startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
@@ -3993,7 +4007,8 @@
if (isTextEditor && attribute != null
&& shouldRestoreImeVisibility(windowToken, softInputMode)) {
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
- attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion);
+ attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
return res;
@@ -4033,7 +4048,8 @@
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4065,7 +4081,8 @@
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4085,7 +4102,8 @@
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4115,7 +4133,8 @@
}
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -5099,9 +5118,8 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
if (mBindingController.supportsStylusHandwriting()
- && getCurMethodLocked() != null && mCurFocusedWindow != null) {
- mHwController.initializeHandwritingSpy(
- mCurTokenDisplayId, mCurFocusedWindow);
+ && getCurMethodLocked() != null) {
+ mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
} else {
mHwController.reset();
}
@@ -5111,14 +5129,15 @@
case MSG_START_HANDWRITING:
synchronized (ImfLock.class) {
IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null) {
+ if (curMethod == null || mCurFocusedWindow == null) {
return true;
}
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- mBindingController.getCurMethodUid());
+ mBindingController.getCurMethodUid(),
+ mCurFocusedWindow);
if (session == null) {
Slog.e(TAG,
"Failed to start handwriting session for requestId: " + msg.arg1);
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index ea99e79..f5c2bbc 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1587,6 +1587,7 @@
if (isGpsEnabled()) {
setGpsEnabled(false);
updateEnabled();
+ restartLocationRequest();
}
}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 21beb96..1bcc21e 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -410,7 +410,8 @@
}
private void processNewLogAccessRequest(LogAccessClient client) {
- boolean isInstrumented = mActivityManagerInternal.isUidCurrentlyInstrumented(client.mUid);
+ boolean isInstrumented = mActivityManagerInternal.getInstrumentationSourceUid(client.mUid)
+ != android.os.Process.INVALID_UID;
// The instrumented apks only run for testing, so we don't check user permission.
if (isInstrumented) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 92703ec..604e8f3 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -60,8 +60,8 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -1159,6 +1159,9 @@
public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
ResultReceiver cb) {
try {
+ final String reason = TAG + ":" + command;
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onCommand(packageName, pid, uid, command, args, cb);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in sendCommand.", e);
@@ -1168,6 +1171,9 @@
public void sendCustomAction(String packageName, int pid, int uid, String action,
Bundle args) {
try {
+ final String reason = TAG + ":custom-" + action;
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onCustomAction(packageName, pid, uid, action, args);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in sendCustomAction.", e);
@@ -1176,6 +1182,9 @@
public void prepare(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":prepare";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepare(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepare.", e);
@@ -1185,6 +1194,9 @@
public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId,
Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromMediaId";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromMediaId.", e);
@@ -1194,6 +1206,9 @@
public void prepareFromSearch(String packageName, int pid, int uid, String query,
Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromSearch";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromSearch.", e);
@@ -1202,6 +1217,9 @@
public void prepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromUri";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromUri.", e);
@@ -1210,6 +1228,9 @@
public void play(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":play";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlay(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in play.", e);
@@ -1219,6 +1240,9 @@
public void playFromMediaId(String packageName, int pid, int uid, String mediaId,
Bundle extras) {
try {
+ final String reason = TAG + ":playFromMediaId";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromMediaId.", e);
@@ -1228,6 +1252,9 @@
public void playFromSearch(String packageName, int pid, int uid, String query,
Bundle extras) {
try {
+ final String reason = TAG + ":playFromSearch";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromSearch.", e);
@@ -1236,6 +1263,9 @@
public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
try {
+ final String reason = TAG + ":playFromUri";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromUri.", e);
@@ -1244,6 +1274,9 @@
public void skipToTrack(String packageName, int pid, int uid, long id) {
try {
+ final String reason = TAG + ":skipToTrack";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSkipToTrack(packageName, pid, uid, id);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in skipToTrack", e);
@@ -1252,6 +1285,9 @@
public void pause(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":pause";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPause(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in pause.", e);
@@ -1260,6 +1296,9 @@
public void stop(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":stop";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onStop(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in stop.", e);
@@ -1268,6 +1307,9 @@
public void next(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":next";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onNext(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in next.", e);
@@ -1276,6 +1318,9 @@
public void previous(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":previous";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrevious(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in previous.", e);
@@ -1284,6 +1329,9 @@
public void fastForward(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":fastForward";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onFastForward(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in fastForward.", e);
@@ -1292,6 +1340,9 @@
public void rewind(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":rewind";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onRewind(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in rewind.", e);
@@ -1300,6 +1351,9 @@
public void seekTo(String packageName, int pid, int uid, long pos) {
try {
+ final String reason = TAG + ":seekTo";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSeekTo(packageName, pid, uid, pos);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in seekTo.", e);
@@ -1308,6 +1362,9 @@
public void rate(String packageName, int pid, int uid, Rating rating) {
try {
+ final String reason = TAG + ":rate";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onRate(packageName, pid, uid, rating);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in rate.", e);
@@ -1316,6 +1373,9 @@
public void setPlaybackSpeed(String packageName, int pid, int uid, float speed) {
try {
+ final String reason = TAG + ":setPlaybackSpeed";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSetPlaybackSpeed(packageName, pid, uid, speed);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in setPlaybackSpeed.", e);
@@ -1325,6 +1385,9 @@
public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService,
int direction) {
try {
+ final String reason = TAG + ":adjustVolume";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
if (asSystemService) {
mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, direction);
@@ -1338,6 +1401,9 @@
public void setVolumeTo(String packageName, int pid, int uid, int value) {
try {
+ final String reason = TAG + ":setVolumeTo";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSetVolumeTo(packageName, pid, uid, value);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in setVolumeTo.", e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 83c576e..9042326 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -58,6 +58,7 @@
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_NOT_PERCEPTIBLE;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -655,10 +656,10 @@
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
- final boolean mEnableAppSettingMigration;
private boolean mForceUserSetOnUpgrade;
private MetricsLogger mMetricsLogger;
+ private NotificationChannelLogger mNotificationChannelLogger;
private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
private final SavePolicyFileRunnable mSavePolicyFile = new SavePolicyFileRunnable();
@@ -1998,12 +1999,6 @@
mNotificationRecordLogger = notificationRecordLogger;
mNotificationInstanceIdSequence = notificationInstanceIdSequence;
Notification.processAllowlistToken = ALLOWLIST_TOKEN;
- // TODO (b/194833441): remove when OS is ready for migration. This flag is checked once
- // rather than having a settings observer because some of the behaviors (e.g. readXml) only
- // happen on reboot
- mEnableAppSettingMigration = Settings.Secure.getIntForUser(
- getContext().getContentResolver(),
- Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1;
}
// TODO - replace these methods with new fields in the VisibleForTesting constructor
@@ -2161,6 +2156,11 @@
mAccessibilityManager = am;
}
+ @VisibleForTesting
+ void setTelecomManager(TelecomManager tm) {
+ mTelecomManager = tm;
+ }
+
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(WorkerHandler handler, RankingHandler rankingHandler,
@@ -2178,7 +2178,7 @@
TelephonyManager telephonyManager, ActivityManagerInternal ami,
MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
UsageStatsManagerInternal usageStatsManagerInternal,
- TelecomManager telecomManager) {
+ TelecomManager telecomManager, NotificationChannelLogger channelLogger) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2275,12 +2275,13 @@
}
});
mPermissionHelper = permissionHelper;
+ mNotificationChannelLogger = channelLogger;
mPreferencesHelper = new PreferencesHelper(getContext(),
mPackageManagerClient,
mRankingHandler,
mZenModeHelper,
mPermissionHelper,
- new NotificationChannelLoggerImpl(),
+ mNotificationChannelLogger,
mAppOps,
new SysUiStatsEvent.BuilderFactory());
mRankingHelper = new RankingHelper(getContext(),
@@ -2362,9 +2363,6 @@
mNotificationEffectsEnabledForAutomotive =
resources.getBoolean(R.bool.config_enableServerNotificationEffectsForAutomotive);
- mPreferencesHelper.lockChannelsForOEM(getContext().getResources().getStringArray(
- com.android.internal.R.array.config_nonBlockableNotificationPackages));
-
mZenModeHelper.setPriorityOnlyDndExemptPackages(getContext().getResources().getStringArray(
com.android.internal.R.array.config_priorityOnlyDndExemptPackages));
@@ -2504,10 +2502,11 @@
LocalServices.getService(ActivityManagerInternal.class),
createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
- AppGlobals.getPermissionManager(), mEnableAppSettingMigration,
+ AppGlobals.getPermissionManager(),
mForceUserSetOnUpgrade),
LocalServices.getService(UsageStatsManagerInternal.class),
- getContext().getSystemService(TelecomManager.class));
+ getContext().getSystemService(TelecomManager.class),
+ new NotificationChannelLoggerImpl());
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -2799,7 +2798,7 @@
}
}
- private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+ void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
boolean fromListener) {
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
// cancel
@@ -2821,11 +2820,9 @@
mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true);
- if (mEnableAppSettingMigration) {
- if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) {
- mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid),
- channel.getImportance() != IMPORTANCE_NONE, true);
- }
+ if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) {
+ mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid),
+ channel.getImportance() != IMPORTANCE_NONE, true);
}
maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
@@ -3474,36 +3471,19 @@
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
- if (mEnableAppSettingMigration) {
- boolean wasEnabled = mPermissionHelper.hasPermission(uid);
- if (wasEnabled == enabled) {
- return;
- }
- mPermissionHelper.setNotificationPermission(
- pkg, UserHandle.getUserId(uid), enabled, true);
- sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
- } else {
- synchronized (mNotificationLock) {
- boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid)
- != NotificationManager.IMPORTANCE_NONE;
-
- if (wasEnabled == enabled) {
- return;
- }
- }
-
- mPreferencesHelper.setEnabled(pkg, uid, enabled);
- // TODO (b/194833441): this is being ignored by app ops now that the permission
- // exists, so send the broadcast manually
- mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
- enabled ? MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-
- sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
+ boolean wasEnabled = mPermissionHelper.hasPermission(uid);
+ if (wasEnabled == enabled) {
+ return;
}
+ mPermissionHelper.setNotificationPermission(
+ pkg, UserHandle.getUserId(uid), enabled, true);
+ sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
+
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.setSubtype(enabled ? 1 : 0));
+ mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (!enabled) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
@@ -3529,8 +3509,6 @@
public void setNotificationsEnabledWithImportanceLockForPackage(
String pkg, int uid, boolean enabled) {
setNotificationsEnabledForPackage(pkg, uid, enabled);
-
- mPreferencesHelper.setAppImportanceLocked(pkg, uid);
}
/**
@@ -3646,14 +3624,10 @@
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- if (mEnableAppSettingMigration) {
- if (mPermissionHelper.hasPermission(Binder.getCallingUid())) {
- return IMPORTANCE_DEFAULT;
- } else {
- return IMPORTANCE_NONE;
- }
+ if (mPermissionHelper.hasPermission(Binder.getCallingUid())) {
+ return IMPORTANCE_DEFAULT;
} else {
- return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
+ return IMPORTANCE_NONE;
}
}
@@ -5885,8 +5859,7 @@
NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
boolean needsOngoingFlag) {
NotificationRecord summaryRecord = null;
- boolean isPermissionFixed = mPermissionHelper.isMigrationEnabled()
- ? mPermissionHelper.isPermissionFixed(pkg, userId) : false;
+ boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
synchronized (mNotificationLock) {
NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey);
if (notificationRecord == null) {
@@ -5895,10 +5868,6 @@
return null;
}
NotificationChannel channel = notificationRecord.getChannel();
- boolean isImportanceFixed = mPermissionHelper.isMigrationEnabled()
- ? isPermissionFixed
- : (channel.isImportanceLockedByOEM()
- || channel.isImportanceLockedByCriticalDeviceFunction());
final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
userId = adjustedSbn.getUser().getIdentifier();
int uid = adjustedSbn.getUid();
@@ -5944,7 +5913,7 @@
System.currentTimeMillis());
summaryRecord = new NotificationRecord(getContext(), summarySbn,
notificationRecord.getChannel());
- summaryRecord.setImportanceFixed(isImportanceFixed);
+ summaryRecord.setImportanceFixed(isPermissionFixed);
summaryRecord.setIsAppImportanceLocked(
notificationRecord.getIsAppImportanceLocked());
summaries.put(pkg, summarySbn.getKey());
@@ -5992,10 +5961,6 @@
@VisibleForTesting
protected ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
getAllUsersNotificationPermissions() {
- // don't bother if migration is not enabled
- if (!mEnableAppSettingMigration) {
- return null;
- }
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> allPermissions = new ArrayMap<>();
final List<UserInfo> allUsers = mUm.getUsers();
// for each of these, get the package notification permissions that are associated
@@ -6509,13 +6474,8 @@
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Slog.e(TAG, noChannelStr);
- boolean appNotificationsOff;
- if (mEnableAppSettingMigration) {
- appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid);
- } else {
- appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
- == NotificationManager.IMPORTANCE_NONE;
- }
+ boolean appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid);
+
if (!appNotificationsOff) {
doChannelWarningToast(notificationUid,
@@ -6527,14 +6487,11 @@
}
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
- r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
+ r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId));
r.setPostSilently(postSilently);
r.setFlagBubbleRemoved(false);
r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
- boolean isImportanceFixed = mPermissionHelper.isMigrationEnabled()
- ? mPermissionHelper.isPermissionFixed(pkg, userId)
- : (channel.isImportanceLockedByOEM()
- || channel.isImportanceLockedByCriticalDeviceFunction());
+ boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
r.setImportanceFixed(isImportanceFixed);
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
@@ -6978,19 +6935,20 @@
private boolean isCallNotification(String pkg, int uid) {
final long identity = Binder.clearCallingIdentity();
try {
- return mTelecomManager.isInManagedCall() || mTelecomManager.isInSelfManagedCall(
- pkg, UserHandle.getUserHandleForUid(uid));
+ if (mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)
+ && mTelecomManager != null) {
+ return mTelecomManager.isInManagedCall()
+ || mTelecomManager.isInSelfManagedCall(
+ pkg, UserHandle.getUserHandleForUid(uid));
+ }
+ return false;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
- if (mEnableAppSettingMigration) {
- return mPermissionHelper.hasPermission(uid);
- } else {
- return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
- }
+ return mPermissionHelper.hasPermission(uid);
}
protected int getNotificationCount(String pkg, int userId, int excludedId,
@@ -7405,6 +7363,7 @@
@Override
public void run() {
boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
+ boolean isCallNotification = isCallNotification(pkg, uid);
synchronized (mNotificationLock) {
try {
NotificationRecord r = null;
@@ -7423,8 +7382,10 @@
final StatusBarNotification n = r.getSbn();
final Notification notification = n.getNotification();
+ boolean isCallNotificationAndCorrectStyle = isCallNotification
+ && notification.isStyle(Notification.CallStyle.class);
- if (!notification.isMediaNotification()
+ if (!(notification.isMediaNotification() || isCallNotificationAndCorrectStyle)
&& (appBanned || isRecordBlockedLocked(r))) {
mUsageStats.registerBlocked(r);
if (DBG) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f979343..cbaf485 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1089,7 +1089,7 @@
}
/**
- * @see PreferencesHelper#getIsAppImportanceLocked(String, int)
+ * @see PermissionHelper#isPermissionUserSet(String, int)
*/
public boolean getIsAppImportanceLocked() {
return mIsAppImportanceLocked;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index a09aa7c..b2fee1e 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -55,31 +55,14 @@
private final PermissionManagerServiceInternal mPmi;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermManager;
- // TODO (b/194833441): Remove this boolean (but keep the isMigrationEnabled() method)
- // when the migration is enabled
- private final boolean mMigrationEnabled;
- private final boolean mIsTv;
private final boolean mForceUserSetOnUpgrade;
public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
- IPermissionManager permManager, boolean migrationEnabled,
- boolean forceUserSetOnUpgrade) {
+ IPermissionManager permManager, boolean forceUserSetOnUpgrade) {
mPmi = pmi;
mPackageManager = packageManager;
mPermManager = permManager;
- mMigrationEnabled = migrationEnabled;
mForceUserSetOnUpgrade = forceUserSetOnUpgrade;
- boolean isTv;
- try {
- isTv = mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0);
- } catch (RemoteException e) {
- isTv = false;
- }
- mIsTv = isTv;
- }
-
- public boolean isMigrationEnabled() {
- return mMigrationEnabled && !mIsTv;
}
/**
@@ -87,7 +70,6 @@
* with a lock held.
*/
public boolean hasPermission(int uid) {
- assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid)
@@ -102,7 +84,6 @@
* Must not be called with a lock held. Format: uid, packageName
*/
Set<Pair<Integer, String>> getAppsRequestingPermission(int userId) {
- assertFlag();
Set<Pair<Integer, String>> requested = new HashSet<>();
List<PackageInfo> pkgs = getInstalledPackages(userId);
for (PackageInfo pi : pkgs) {
@@ -140,7 +121,6 @@
* with a lock held. Format: uid, packageName.
*/
Set<Pair<Integer, String>> getAppsGrantedPermission(int userId) {
- assertFlag();
Set<Pair<Integer, String>> granted = new HashSet<>();
ParceledListSlice<PackageInfo> parceledList = null;
try {
@@ -162,7 +142,6 @@
public @NonNull
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
getNotificationPermissionValues(int userId) {
- assertFlag();
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
@@ -189,7 +168,6 @@
*/
public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
boolean userSet, boolean reviewRequired) {
- assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
// Do not change the permission if the package doesn't request it, do not change fixed
@@ -230,7 +208,6 @@
* restoring a pre-T backup on a T+ device
*/
public void setNotificationPermission(PackagePermission pkgPerm) {
- assertFlag();
if (pkgPerm == null || pkgPerm.packageName == null) {
return;
}
@@ -242,7 +219,6 @@
}
public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
- assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
try {
@@ -260,7 +236,6 @@
}
boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
- assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
try {
@@ -278,7 +253,6 @@
}
boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) {
- assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
try {
@@ -297,7 +271,6 @@
private boolean packageRequestsNotificationPermission(String packageName,
@UserIdInt int userId) {
- assertFlag();
try {
String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS,
userId).requestedPermissions;
@@ -308,12 +281,6 @@
return false;
}
- private void assertFlag() {
- if (!mMigrationEnabled) {
- throw new IllegalStateException("Method called without checking flag value");
- }
- }
-
public static class PackagePermission {
public final String packageName;
public final @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index ef3c770..4e3fbaa 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -209,11 +209,7 @@
mAppOps = appOpsManager;
mStatsEventBuilderFactory = statsEventBuilderFactory;
- if (mPermissionHelper.isMigrationEnabled()) {
- XML_VERSION = 4;
- } else {
- XML_VERSION = 2;
- }
+ XML_VERSION = 4;
updateBadgingEnabled();
updateBubblesEnabled();
@@ -230,8 +226,7 @@
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
- boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
- && mPermissionHelper.isMigrationEnabled();
+ boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
// make a note that we should show the notification at some point.
// it shouldn't be possible for the user to already have seen it, as the XML version
@@ -393,8 +388,6 @@
hasUserConfiguredSettings(r));
pkgPerms.add(pkgPerm);
}
- } else if (!mPermissionHelper.isMigrationEnabled()) {
- r.importance = appImportance;
}
} catch (Exception e) {
Slog.w(TAG, "Failed to restore pkg", e);
@@ -417,16 +410,8 @@
} else {
channel.populateFromXml(parser);
}
- if (!mPermissionHelper.isMigrationEnabled()) {
- channel.setImportanceLockedByCriticalDeviceFunction(
- r.defaultAppLockedImportance);
- channel.setImportanceLockedByOEM(r.oemLockedImportance);
- if (!channel.isImportanceLockedByOEM()) {
- if (r.oemLockedChannels.contains(channel.getId())) {
- channel.setImportanceLockedByOEM(true);
- }
- }
- }
+ channel.setImportanceLockedByCriticalDeviceFunction(
+ r.defaultAppLockedImportance);
if (isShortcutOk(channel) && isDeletionOk(channel)) {
r.channels.put(id, channel);
@@ -604,7 +589,7 @@
out.endTag(null, TAG_STATUS_ICONS);
}
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
- if (mPermissionHelper.isMigrationEnabled() && forBackup) {
+ if (forBackup) {
notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
}
@@ -736,28 +721,6 @@
}
}
- /**
- * Gets importance.
- */
- @Override
- public int getImportance(String packageName, int uid) {
- synchronized (mPackagePreferences) {
- return getOrCreatePackagePreferencesLocked(packageName, uid).importance;
- }
- }
-
- /**
- * Returns whether the importance of the corresponding notification is user-locked and shouldn't
- * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
- * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
- */
- public boolean getIsAppImportanceLocked(String packageName, int uid) {
- synchronized (mPackagePreferences) {
- int userLockedFields = getOrCreatePackagePreferencesLocked(packageName, uid).lockedAppFields;
- return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
- }
- }
-
@Override
public boolean canShowBadge(String packageName, int uid) {
synchronized (mPackagePreferences) {
@@ -1043,16 +1006,10 @@
: NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
- if (!mPermissionHelper.isMigrationEnabled()) {
- channel.setImportanceLockedByOEM(r.oemLockedImportance);
- if (!channel.isImportanceLockedByOEM()) {
- if (r.oemLockedChannels.contains(channel.getId())) {
- channel.setImportanceLockedByOEM(true);
- }
- }
- channel.setImportanceLockedByCriticalDeviceFunction(
- r.defaultAppLockedImportance);
- }
+
+ channel.setImportanceLockedByCriticalDeviceFunction(
+ r.defaultAppLockedImportance);
+
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(
NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
@@ -1133,33 +1090,15 @@
updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
}
- if (mPermissionHelper.isMigrationEnabled()) {
- if (mPermissionHelper.isPermissionFixed(r.pkg, UserHandle.getUserId(r.uid))
- && !(channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE)) {
- updatedChannel.setImportance(channel.getImportance());
- }
- } else {
- // no importance updates are allowed if OEM blocked it
- updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
- if (updatedChannel.isImportanceLockedByOEM()) {
- updatedChannel.setImportance(channel.getImportance());
- }
- updatedChannel.setImportanceLockedByCriticalDeviceFunction(
- r.defaultAppLockedImportance);
- if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()
- && updatedChannel.getImportance() == IMPORTANCE_NONE) {
- updatedChannel.setImportance(channel.getImportance());
- }
+ if ((mPermissionHelper.isPermissionFixed(r.pkg, UserHandle.getUserId(r.uid))
+ || channel.isImportanceLockedByCriticalDeviceFunction())
+ && !(channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE)) {
+ updatedChannel.setImportance(channel.getImportance());
}
r.channels.put(updatedChannel.getId(), updatedChannel);
if (onlyHasDefaultChannel(pkg, uid)) {
- if (!mPermissionHelper.isMigrationEnabled()) {
- // copy settings to app level so they are inherited by new channels
- // when the app migrates
- r.importance = updatedChannel.getImportance();
- }
r.priority = updatedChannel.canBypassDnd()
? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
r.visibility = updatedChannel.getLockscreenVisibility();
@@ -1328,61 +1267,8 @@
mHideSilentStatusBarIcons = hide;
}
- public void lockChannelsForOEM(String[] appOrChannelList) {
- if (mPermissionHelper.isMigrationEnabled()) {
- return;
- }
- if (appOrChannelList == null) {
- return;
- }
- for (String appOrChannel : appOrChannelList) {
- if (!TextUtils.isEmpty(appOrChannel)) {
- String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
- if (appSplit != null && appSplit.length > 0) {
- String appName = appSplit[0];
- String channelId = appSplit.length == 2 ? appSplit[1] : null;
-
- synchronized (mPackagePreferences) {
- boolean foundApp = false;
- for (PackagePreferences r : mPackagePreferences.values()) {
- if (r.pkg.equals(appName)) {
- foundApp = true;
- if (channelId == null) {
- // lock all channels for the app
- r.oemLockedImportance = true;
- for (NotificationChannel channel : r.channels.values()) {
- channel.setImportanceLockedByOEM(true);
- }
- } else {
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null) {
- channel.setImportanceLockedByOEM(true);
- }
- // Also store the locked channels on the record, so they aren't
- // temporarily lost when data is cleared on the package
- r.oemLockedChannels.add(channelId);
- }
- }
- }
- if (!foundApp) {
- List<String> channels =
- mOemLockedApps.getOrDefault(appName, new ArrayList<>());
- if (channelId != null) {
- channels.add(channelId);
- }
- mOemLockedApps.put(appName, channels);
- }
- }
- }
- }
- }
- }
-
public void updateDefaultApps(int userId, ArraySet<String> toRemove,
ArraySet<Pair<String, Integer>> toAdd) {
- if (mPermissionHelper.isMigrationEnabled()) {
- return;
- }
synchronized (mPackagePreferences) {
for (PackagePreferences p : mPackagePreferences.values()) {
if (userId == UserHandle.getUserId(p.uid)) {
@@ -1802,20 +1688,8 @@
}
for (int i = candidatePkgs.size() - 1; i >= 0; i--) {
Pair<String, Integer> app = candidatePkgs.valueAt(i);
- if (mPermissionHelper.isMigrationEnabled()) {
- if (!mPermissionHelper.hasPermission(app.second)) {
- candidatePkgs.removeAt(i);
- }
- } else {
- synchronized (mPackagePreferences) {
- PackagePreferences r = getPackagePreferencesLocked(app.first, app.second);
- if (r == null) {
- continue;
- }
- if (r.importance == IMPORTANCE_NONE) {
- candidatePkgs.removeAt(i);
- }
- }
+ if (!mPermissionHelper.hasPermission(app.second)) {
+ candidatePkgs.removeAt(i);
}
}
boolean haveBypassingApps = candidatePkgs.size() > 0;
@@ -1861,27 +1735,6 @@
}
/**
- * Sets importance.
- */
- @Override
- public void setImportance(String pkgName, int uid, int importance) {
- synchronized (mPackagePreferences) {
- getOrCreatePackagePreferencesLocked(pkgName, uid).importance = importance;
- }
- updateConfig();
- }
-
- public void setEnabled(String packageName, int uid, boolean enabled) {
- boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
- if (wasEnabled == enabled) {
- return;
- }
- setImportance(packageName, uid,
- enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
- mNotificationChannelLogger.logAppNotificationsAllowed(uid, packageName, enabled);
- }
-
- /**
* Sets whether any notifications from the app, represented by the given {@code pkgName} and
* {@code uid}, have their importance locked by the user. Locked notifications don't get
* considered for sentiment adjustments (and thus never show a blocking helper).
@@ -2055,23 +1908,15 @@
pw.print(" (");
pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
pw.print(')');
- if (!mPermissionHelper.isMigrationEnabled()) {
- if (r.importance != DEFAULT_IMPORTANCE) {
- pw.print(" importance=");
- pw.print(NotificationListenerService.Ranking.importanceToString(
- r.importance));
- }
- } else {
- Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
- if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
- pw.print(" importance=");
- pw.print(NotificationListenerService.Ranking.importanceToString(
- packagePermissions.get(key).first
- ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
- pw.print(" userSet=");
- pw.print(packagePermissions.get(key).second);
- pkgsWithPermissionsToHandle.remove(key);
- }
+ Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
+ if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
+ pw.print(" importance=");
+ pw.print(NotificationListenerService.Ranking.importanceToString(
+ packagePermissions.get(key).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+ pw.print(" userSet=");
+ pw.print(packagePermissions.get(key).second);
+ pkgsWithPermissionsToHandle.remove(key);
}
if (r.priority != DEFAULT_PRIORITY) {
pw.print(" priority=");
@@ -2111,7 +1956,7 @@
}
}
// Handle any remaining packages with permissions
- if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) {
+ if (pkgsWithPermissionsToHandle != null) {
for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
// p.first is the uid of this package; p.second is the package name
if (filter.matches(p.second)) {
@@ -2151,16 +1996,12 @@
proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
proto.write(RankingHelperProto.RecordProto.UID, r.uid);
- if (mPermissionHelper.isMigrationEnabled()) {
- Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
- if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
- proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
- packagePermissions.get(key).first
- ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
- pkgsWithPermissionsToHandle.remove(key);
- }
- } else {
- proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
+ Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
+ if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
+ proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
+ packagePermissions.get(key).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+ pkgsWithPermissionsToHandle.remove(key);
}
proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
@@ -2177,7 +2018,7 @@
}
}
- if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) {
+ if (pkgsWithPermissionsToHandle != null) {
for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
if (filter.matches(p.second)) {
fToken = proto.start(fieldId);
@@ -2217,25 +2058,22 @@
// collect whether this package's importance info was user-set for later, if needed
// before the migration is enabled, this will simply default to false in all cases.
boolean importanceIsUserSet = false;
- if (mPermissionHelper.isMigrationEnabled()) {
- // Even if this package's data is not present, we need to write something;
- // so default to IMPORTANCE_NONE, since if PM doesn't know about the package
- // for some reason, notifications are not allowed.
- int importance = IMPORTANCE_NONE;
- Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
- if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
- Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key);
- importance = permissionPair.first
- ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
- // cache the second value for writing later
- importanceIsUserSet = permissionPair.second;
+ // Even if this package's data is not present, we need to write something;
+ // so default to IMPORTANCE_NONE, since if PM doesn't know about the package
+ // for some reason, notifications are not allowed.
+ int importance = IMPORTANCE_NONE;
+ Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
+ if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
+ Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key);
+ importance = permissionPair.first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
+ // cache the second value for writing later
+ importanceIsUserSet = permissionPair.second;
- pkgsWithPermissionsToHandle.remove(key);
- }
- event.writeInt(importance);
- } else {
- event.writeInt(r.importance);
+ pkgsWithPermissionsToHandle.remove(key);
}
+ event.writeInt(importance);
+
event.writeInt(r.visibility);
event.writeInt(r.lockedAppFields);
event.writeBoolean(importanceIsUserSet); // optional bool user_set_importance = 5;
@@ -2244,7 +2082,7 @@
}
// handle remaining packages with PackageManager permissions but not local settings
- if (mPermissionHelper.isMigrationEnabled() && pkgPermissions != null) {
+ if (pkgPermissions != null) {
for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) {
break;
@@ -2357,22 +2195,14 @@
try {
PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
PackagePreferences.put("packageName", r.pkg);
- if (mPermissionHelper.isMigrationEnabled()) {
- Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
- if (pkgPermissions != null
- && pkgsWithPermissionsToHandle.contains(key)) {
- PackagePreferences.put("importance",
- NotificationListenerService.Ranking.importanceToString(
- pkgPermissions.get(key).first
- ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
- pkgsWithPermissionsToHandle.remove(key);
- }
- } else {
- if (r.importance != DEFAULT_IMPORTANCE) {
- PackagePreferences.put("importance",
- NotificationListenerService.Ranking.importanceToString(
- r.importance));
- }
+ Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
+ if (pkgPermissions != null
+ && pkgsWithPermissionsToHandle.contains(key)) {
+ PackagePreferences.put("importance",
+ NotificationListenerService.Ranking.importanceToString(
+ pkgPermissions.get(key).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+ pkgsWithPermissionsToHandle.remove(key);
}
if (r.priority != DEFAULT_PRIORITY) {
PackagePreferences.put("priority",
@@ -2404,7 +2234,7 @@
}
// handle packages for which there are permissions but no local settings
- if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) {
+ if (pkgsWithPermissionsToHandle != null) {
for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
if (filter == null || filter.matches(p.second)) {
JSONObject PackagePreferences = new JSONObject();
@@ -2443,8 +2273,7 @@
public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter,
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONArray bans = new JSONArray();
- Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled()
- ? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans();
+ Map<Integer, String> packageBans = getPermissionBasedPackageBans(pkgPermissions);
for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
final int userId = UserHandle.getUserId(ban.getKey());
final String packageName = ban.getValue();
@@ -2597,7 +2426,7 @@
synchronized (mPackagePreferences) {
mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
}
- if (mPermissionHelper.isMigrationEnabled() && r.migrateToPm) {
+ if (r.migrateToPm) {
try {
PackagePermission p = new PackagePermission(
r.pkg, UserHandle.getUserId(r.uid),
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 3982593..3e9d90c 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -24,8 +24,6 @@
public interface RankingConfig {
- void setImportance(String packageName, int uid, int importance);
- int getImportance(String packageName, int uid);
void setShowBadge(String packageName, int uid, boolean showBadge);
boolean canShowBadge(String packageName, int uid);
boolean badgingEnabled(UserHandle userHandle);
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index 2824585..3911994 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -34,13 +34,14 @@
import android.content.pm.ApkChecksum;
import android.content.pm.Checksum;
import android.content.pm.IOnChecksumsReadyListener;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -63,7 +64,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.security.VerityUtils;
-import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.ByteArrayOutputStream;
@@ -637,9 +637,18 @@
return null;
}
+ private static boolean containsFile(File dir, String filePath) {
+ if (dir == null) {
+ return false;
+ }
+ return FileUtils.contains(dir.getAbsolutePath(), filePath);
+ }
+
private static ApkChecksum extractHashFromFS(String split, String filePath) {
// verity first
- {
+ // Skip /product folder.
+ // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules.
+ if (!containsFile(Environment.getProductDirectory(), filePath)) {
byte[] hash = VerityUtils.getFsverityRootHash(filePath);
if (hash != null) {
return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, hash);
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index d26a1ac..9ea0192 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -91,17 +91,21 @@
// Possible return codes of individual optimization steps.
/** Ok status: Optimizations finished, All packages were processed, can continue */
- private static final int STATUS_OK = 0;
+ /* package */ static final int STATUS_OK = 0;
/** Optimizations should be aborted. Job scheduler requested it. */
- private static final int STATUS_ABORT_BY_CANCELLATION = 1;
+ /* package */ static final int STATUS_ABORT_BY_CANCELLATION = 1;
/** Optimizations should be aborted. No space left on device. */
- private static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
+ /* package */ static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
/** Optimizations should be aborted. Thermal throttling level too high. */
- private static final int STATUS_ABORT_THERMAL = 3;
+ /* package */ static final int STATUS_ABORT_THERMAL = 3;
/** Battery level too low */
- private static final int STATUS_ABORT_BATTERY = 4;
- /** {@link PackageDexOptimizer#DEX_OPT_FAILED} case */
- private static final int STATUS_DEX_OPT_FAILED = 5;
+ /* package */ static final int STATUS_ABORT_BATTERY = 4;
+ /**
+ * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed
+ * compilation during the job. Note that the failure will not be permanent as the next dexopt
+ * job will exclude those failed packages.
+ */
+ /* package */ static final int STATUS_DEX_OPT_FAILED = 5;
@IntDef(prefix = {"STATUS_"}, value = {
STATUS_OK,
@@ -525,7 +529,10 @@
}
}
- /** Returns true if completed */
+ /**
+ * Returns whether we've successfully run the job. Note that it will return true even if some
+ * packages may have failed compiling.
+ */
private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs,
boolean isPostBootUpdate) {
synchronized (mLock) {
@@ -541,7 +548,7 @@
mLastExecutionDurationMs = SystemClock.elapsedRealtime() - mLastExecutionStartTimeMs;
}
- return status == STATUS_OK;
+ return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
}
/** Gets the size of the directory. It uses recursion to go over all files. */
@@ -661,6 +668,11 @@
ArraySet<String> updatedPackages, boolean isPostBootUpdate) {
boolean supportSecondaryDex = mInjector.supportSecondaryDex();
+ // Keep the error if there is any error from any package.
+ @Status int status = STATUS_OK;
+
+ // Other than cancellation, all packages will be processed even if an error happens
+ // in a package.
for (String pkg : pkgs) {
int abortCode = abortIdleOptimizations(lowStorageThreshold);
if (abortCode != STATUS_OK) {
@@ -670,10 +682,13 @@
@DexOptResult int primaryResult =
optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate);
+ if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
+ return STATUS_ABORT_BY_CANCELLATION;
+ }
if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
- } else if (primaryResult != PackageDexOptimizer.DEX_OPT_SKIPPED) {
- return convertPackageDexOptimizerStatusToInternal(primaryResult);
+ } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
+ status = convertPackageDexOptimizerStatusToInternal(primaryResult);
}
if (!supportSecondaryDex) {
@@ -682,12 +697,14 @@
@DexOptResult int secondaryResult =
optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate);
- if (secondaryResult != PackageDexOptimizer.DEX_OPT_PERFORMED
- && secondaryResult != PackageDexOptimizer.DEX_OPT_SKIPPED) {
- return convertPackageDexOptimizerStatusToInternal(secondaryResult);
+ if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
+ return STATUS_ABORT_BY_CANCELLATION;
+ }
+ if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
+ status = convertPackageDexOptimizerStatusToInternal(secondaryResult);
}
}
- return STATUS_OK;
+ return status;
}
/**
@@ -779,9 +796,9 @@
@DexOptResult
private int performDexOptPrimary(String pkg, int reason,
int dexoptFlags) {
+ DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, dexoptFlags);
return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
- () -> mDexOptHelper.performDexOptWithStatus(
- new DexoptOptions(pkg, reason, dexoptFlags)));
+ () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
}
@DexOptResult
@@ -791,7 +808,7 @@
dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
() -> mDexOptHelper.performDexOpt(dexoptOptions)
- ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+ ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
);
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index ed71f1e..9d1f070 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -197,7 +197,7 @@
final BroadcastOptions bOptions = getTemporaryAppAllowlistBroadcastOptions(
REASON_LOCKED_BOOT_COMPLETED);
am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null,
- requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+ requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE,
bOptions.toBundle(), false, false, userId);
// Deliver BOOT_COMPLETED only if user is unlocked
@@ -207,7 +207,7 @@
bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null,
- requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+ requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE,
bOptions.toBundle(), false, false, userId);
}
} catch (RemoteException e) {
@@ -263,7 +263,7 @@
};
try {
am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null,
- requiredPermissions, null, android.app.AppOpsManager.OP_NONE, null, false,
+ requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, null, false,
false, UserHandle.USER_ALL);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -301,7 +301,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
am.broadcastIntentWithFeature(null, null, intent, null, null,
- 0, null, null, null, null, android.app.AppOpsManager.OP_NONE,
+ 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
null, false, false, userId);
} catch (RemoteException e) {
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index f1296e0..8c33dd9 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -29,9 +29,7 @@
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
-import android.Manifest;
import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.Intent;
@@ -44,7 +42,6 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -253,60 +250,10 @@
numberOfPackagesFailed};
}
- /**
- * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
- * compiles it if needed.
- */
- private void checkAndDexOptSystemUi() {
- Computer snapshot = mPm.snapshotComputer();
- String sysUiPackageName =
- mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
- AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
- if (pkg == null) {
- Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
- return;
- }
-
- boolean useProfileForDexopt = false;
- File profileFile = new File(getPrebuildProfilePath(pkg));
- // Copy the profile to the reference profile path if it exists. Installd can only use a
- // profile at the reference profile path for dexopt.
- if (profileFile.exists()) {
- try {
- synchronized (mPm.mInstallLock) {
- if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- useProfileForDexopt = true;
- } else {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
- }
- }
-
- // It could also be after mainline update, but we're not introducing a new reason just for
- // this special case.
- performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
- useProfileForDexopt ? "speed-profile" : "speed", null /* splitName */,
- 0 /* dexoptFlags */));
- }
-
- @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
- // The default is "true".
- if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) {
- // System UI is important to user experience, so we check it on every boot. It may need
- // to be re-compiled after a mainline update or an OTA.
- // TODO(b/227310505): Only do this after a mainline update or an OTA.
- checkAndDexOptSystemUi();
- }
-
// We need to re-extract after an OTA.
boolean causeUpgrade = mPm.isDeviceUpgrading();
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 320b06f..9de667f 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -44,6 +44,9 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
public class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -118,9 +121,13 @@
public static final int FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES =
IInstalld.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
- private final boolean mIsolated;
+ private static final long CONNECT_RETRY_DELAY_MS = DateUtils.SECOND_IN_MILLIS;
+ private static final long CONNECT_WAIT_MS = 10 * DateUtils.SECOND_IN_MILLIS;
- private volatile IInstalld mInstalld;
+ private final boolean mIsolated;
+ private volatile boolean mDeferSetFirstBoot;
+ private volatile IInstalld mInstalld = null;
+ private volatile CompletableFuture<IInstalld> mInstalldFuture = new CompletableFuture<>();
private volatile Object mWarnIfHeld;
public Installer(Context context) {
@@ -149,6 +156,7 @@
public void onStart() {
if (mIsolated) {
mInstalld = null;
+ mInstalldFuture = null;
} else {
connect();
}
@@ -168,9 +176,12 @@
}
if (binder != null) {
- mInstalld = IInstalld.Stub.asInterface(binder);
+ IInstalld installd = IInstalld.Stub.asInterface(binder);
+ mInstalld = installd;
+ mInstalldFuture.complete(installd);
try {
invalidateMounts();
+ executeDeferredActions();
} catch (InstallerException ignored) {
}
} else {
@@ -180,6 +191,15 @@
}
/**
+ * Perform any deferred actions on mInstalld while the connection could not be made.
+ */
+ private void executeDeferredActions() throws InstallerException {
+ if (mDeferSetFirstBoot) {
+ setFirstBoot();
+ }
+ }
+
+ /**
* Do several pre-flight checks before making a remote call.
*
* @return if the remote call should continue.
@@ -192,9 +212,18 @@
if (mIsolated) {
Slog.i(TAG, "Ignoring request because this installer is isolated");
return false;
- } else {
- return true;
}
+
+ if (mInstalld == null && mInstalldFuture != null) {
+ try {
+ Slog.i(TAG, "installd not ready, waiting for: " + CONNECT_WAIT_MS + "ms");
+ mInstalld = mInstalldFuture.get(CONNECT_WAIT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(TAG, "Ignoring request because this installer is not initialized", e);
+ }
+ }
+
+ return mInstalld != null;
}
// We explicitly do NOT set previousAppId because the default value should always be 0.
@@ -291,8 +320,15 @@
return;
}
try {
- mInstalld.setFirstBoot();
- } catch (RemoteException e) {
+ // mInstalld might be null if the connection could not be established.
+ if (mInstalld != null) {
+ mInstalld.setFirstBoot();
+ } else {
+ // if it is null while trying to set the first boot, set a flag to try and set the
+ // first boot when the connection is eventually established
+ mDeferSetFirstBoot = true;
+ }
+ } catch (Exception e) {
throw InstallerException.from(e);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index ec6443d..652847a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -315,9 +315,9 @@
@Deprecated
public final List<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int filterCallingUid, int userId) {
- return getResolveIntentHelper().queryIntentReceiversInternal(
- snapshot(), intent, resolvedType, flags, userId, filterCallingUid);
+ int filterCallingUid, int userId, boolean forSend) {
+ return getResolveIntentHelper().queryIntentReceiversInternal(snapshot(), intent,
+ resolvedType, flags, userId, filterCallingUid, forSend);
}
@Override
@@ -352,10 +352,9 @@
@Override
@Deprecated
- public final void setDeviceOwnerProtectedPackages(
- String deviceOwnerPackageName, List<String> packageNames) {
- getProtectedPackages().setDeviceOwnerProtectedPackages(
- deviceOwnerPackageName, packageNames);
+ public final void setOwnerProtectedPackages(
+ @UserIdInt int userId, @NonNull List<String> packageNames) {
+ getProtectedPackages().setOwnerProtectedPackages(userId, packageNames);
}
@Override
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index bf46129..e923988 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -16,11 +16,11 @@
package com.android.server.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.UserHandle;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -56,7 +56,7 @@
@Nullable
@GuardedBy("this")
- private final ArrayMap<String, Set<String>> mDeviceOwnerProtectedPackages = new ArrayMap<>();
+ private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>();
private final Context mContext;
@@ -79,13 +79,13 @@
: profileOwnerPackages.clone();
}
- /** Sets the protected packages for the device owner. */
- public synchronized void setDeviceOwnerProtectedPackages(
- String deviceOwnerPackageName, List<String> packageNames) {
+ /** Sets packages protected by a device or profile owner. */
+ public synchronized void setOwnerProtectedPackages(
+ @UserIdInt int userId, @NonNull List<String> packageNames) {
if (packageNames.isEmpty()) {
- mDeviceOwnerProtectedPackages.remove(deviceOwnerPackageName);
+ mOwnerProtectedPackages.remove(userId);
} else {
- mDeviceOwnerProtectedPackages.put(deviceOwnerPackageName, new ArraySet<>(packageNames));
+ mOwnerProtectedPackages.put(userId, new ArraySet<>(packageNames));
}
}
@@ -123,19 +123,24 @@
* <p>A protected package means that, apart from the package owner, no system or privileged apps
* can modify its data or package state.
*/
- private synchronized boolean isProtectedPackage(String packageName) {
+ private synchronized boolean isProtectedPackage(@UserIdInt int userId, String packageName) {
return packageName != null && (packageName.equals(mDeviceProvisioningPackage)
- || isDeviceOwnerProtectedPackage(packageName));
+ || isOwnerProtectedPackage(userId, packageName));
}
- /** Returns {@code true} if the given package is a protected package set by any device owner. */
- private synchronized boolean isDeviceOwnerProtectedPackage(String packageName) {
- for (Set<String> protectedPackages : mDeviceOwnerProtectedPackages.values()) {
- if (protectedPackages.contains(packageName)) {
- return true;
- }
- }
- return false;
+ /**
+ * Returns {@code true} if the given package is a protected package set by any device or
+ * profile owner.
+ */
+ private synchronized boolean isOwnerProtectedPackage(
+ @UserIdInt int userId, String packageName) {
+ return isPackageProtectedForUser(UserHandle.USER_ALL, packageName)
+ || isPackageProtectedForUser(userId, packageName);
+ }
+
+ private synchronized boolean isPackageProtectedForUser(int userId, String packageName) {
+ int userIdx = mOwnerProtectedPackages.indexOfKey(userId);
+ return userIdx >= 0 && mOwnerProtectedPackages.valueAt(userIdx).contains(packageName);
}
/**
@@ -146,7 +151,7 @@
*/
public boolean isPackageStateProtected(@UserIdInt int userId, String packageName) {
return hasDeviceOwnerOrProfileOwner(userId, packageName)
- || isProtectedPackage(packageName);
+ || isProtectedPackage(userId, packageName);
}
/**
@@ -155,6 +160,6 @@
*/
public boolean isPackageDataProtected(@UserIdInt int userId, String packageName) {
return hasDeviceOwnerOrProfileOwner(userId, packageName)
- || isProtectedPackage(packageName);
+ || isProtectedPackage(userId, packageName);
}
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index b74670b..2db1c20 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -306,16 +306,32 @@
return new IntentSender(target);
}
- // In this method, we have to know the actual calling UID, but in some cases Binder's
- // call identity is removed, so the UID has to be passed in explicitly.
- public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
+ /**
+ * Retrieve all receivers that can handle a broadcast of the given intent.
+ * @param queryingUid the results will be filtered in the context of this UID instead.
+ */
+ @NonNull
+ public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
- int filterCallingUid) {
+ int queryingUid) {
+ return queryIntentReceiversInternal(computer, intent, resolvedType, flags, userId,
+ queryingUid, false);
+ }
+
+ /**
+ * @see PackageManagerInternal#queryIntentReceivers(Intent, String, long, int, int, boolean)
+ */
+ @NonNull
+ public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
+ String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
+ int filterCallingUid, boolean forSend) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
- computer.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
- false /*checkShell*/, "query intent receivers");
- final String instantAppPkgName = computer.getInstantAppPackageName(filterCallingUid);
- flags = computer.updateFlagsForResolve(flags, userId, filterCallingUid,
+ // The identity used to filter the receiver components
+ final int queryingUid = forSend ? Process.SYSTEM_UID : filterCallingUid;
+ computer.enforceCrossUserPermission(queryingUid, userId,
+ false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers");
+ final String instantAppPkgName = computer.getInstantAppPackageName(queryingUid);
+ flags = computer.updateFlagsForResolve(flags, userId, queryingUid,
false /*includeInstantApps*/,
computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
resolvedType, flags));
@@ -397,7 +413,7 @@
list, true, originalIntent, resolvedType, filterCallingUid);
}
- return computer.applyPostResolutionFilter(list, instantAppPkgName, false, filterCallingUid,
+ return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
false, userId, intent);
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ba4d09f..6400502 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4579,7 +4579,7 @@
pw.print(" (override=true)");
}
pw.println();
- if (ps.getPkg().getQueriesPackages().isEmpty()) {
+ if (!ps.getPkg().getQueriesPackages().isEmpty()) {
pw.append(prefix).append(" queriesPackages=")
.println(ps.getPkg().getQueriesPackages());
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 4df54b7..fa0c6c3 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -166,18 +166,19 @@
* An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
@GuardedBy("mLock")
- final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
* IDs.
*/
@GuardedBy("mLock")
- private ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
+ private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
/**
* All the share targets from the package
*/
+ @GuardedBy("mLock")
private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
/**
@@ -231,7 +232,9 @@
}
public int getShortcutCount() {
- return mShortcuts.size();
+ synchronized (mLock) {
+ return mShortcuts.size();
+ }
}
@Override
@@ -272,7 +275,9 @@
@Nullable
public ShortcutInfo findShortcutById(@Nullable final String id) {
if (id == null) return null;
- return mShortcuts.get(id);
+ synchronized (mLock) {
+ return mShortcuts.get(id);
+ }
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -347,11 +352,14 @@
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
- if (shortcut != null) {
- removeIcon(shortcut);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
- | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
+ final ShortcutInfo shortcut;
+ synchronized (mLock) {
+ shortcut = mShortcuts.remove(id);
+ if (shortcut != null) {
+ removeIcon(shortcut);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
+ | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
+ }
}
return shortcut;
}
@@ -524,14 +532,16 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.isDynamic() && si.isVisibleToPublisher()) {
- changed = true;
+ synchronized (mLock) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.isDynamic() && si.isVisibleToPublisher()) {
+ changed = true;
- si.setTimestamp(now);
- si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- si.setRank(0); // It may still be pinned, so clear the rank.
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ }
}
}
removeAllShortcutsAsync();
@@ -874,59 +884,63 @@
*/
public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
@NonNull IntentFilter filter) {
- final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
- for (int i = 0; i < mShareTargets.size(); i++) {
- final ShareTargetInfo target = mShareTargets.get(i);
- for (ShareTargetInfo.TargetData data : target.mTargetData) {
- if (filter.hasDataType(data.mMimeType)) {
- // Matched at least with one data type
- matchedTargets.add(target);
- break;
- }
- }
- }
-
- if (matchedTargets.isEmpty()) {
- return new ArrayList<>();
- }
-
- // Get the list of all dynamic shortcuts in this package.
- final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are
- // included in the result
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
- mShortcutUser.mService.mContext.getPackageName(),
- 0, /*getPinnedByAnyLauncher=*/ false);
-
- final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
- for (int i = 0; i < shortcuts.size(); i++) {
- final Set<String> categories = shortcuts.get(i).getCategories();
- if (categories == null || categories.isEmpty()) {
- continue;
- }
- for (int j = 0; j < matchedTargets.size(); j++) {
- // Shortcut must have all of share target categories
- boolean hasAllCategories = true;
- final ShareTargetInfo target = matchedTargets.get(j);
- for (int q = 0; q < target.mCategories.length; q++) {
- if (!categories.contains(target.mCategories[q])) {
- hasAllCategories = false;
+ synchronized (mLock) {
+ final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
+ for (int i = 0; i < mShareTargets.size(); i++) {
+ final ShareTargetInfo target = mShareTargets.get(i);
+ for (ShareTargetInfo.TargetData data : target.mTargetData) {
+ if (filter.hasDataType(data.mMimeType)) {
+ // Matched at least with one data type
+ matchedTargets.add(target);
break;
}
}
- if (hasAllCategories) {
- result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i),
- new ComponentName(getPackageName(), target.mTargetClass)));
- break;
+ }
+
+ if (matchedTargets.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ // Get the list of all dynamic shortcuts in this package.
+ final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
+ // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are
+ // included in the result
+ findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
+ ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
+ mShortcutUser.mService.mContext.getPackageName(),
+ 0, /*getPinnedByAnyLauncher=*/ false);
+
+ final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
+ for (int i = 0; i < shortcuts.size(); i++) {
+ final Set<String> categories = shortcuts.get(i).getCategories();
+ if (categories == null || categories.isEmpty()) {
+ continue;
+ }
+ for (int j = 0; j < matchedTargets.size(); j++) {
+ // Shortcut must have all of share target categories
+ boolean hasAllCategories = true;
+ final ShareTargetInfo target = matchedTargets.get(j);
+ for (int q = 0; q < target.mCategories.length; q++) {
+ if (!categories.contains(target.mCategories[q])) {
+ hasAllCategories = false;
+ break;
+ }
+ }
+ if (hasAllCategories) {
+ result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i),
+ new ComponentName(getPackageName(), target.mTargetClass)));
+ break;
+ }
}
}
+ return result;
}
- return result;
}
public boolean hasShareTargets() {
- return !mShareTargets.isEmpty();
+ synchronized (mLock) {
+ return !mShareTargets.isEmpty();
+ }
}
/**
@@ -935,38 +949,40 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShareTargets.isEmpty()) {
- return 0;
- }
-
- // Get the list of all dynamic shortcuts in this package
- final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
- int sharingShortcutCount = 0;
- for (int i = 0; i < shortcuts.size(); i++) {
- final Set<String> categories = shortcuts.get(i).getCategories();
- if (categories == null || categories.isEmpty()) {
- continue;
+ synchronized (mLock) {
+ if (mShareTargets.isEmpty()) {
+ return 0;
}
- for (int j = 0; j < mShareTargets.size(); j++) {
- // A SharingShortcut must have all of share target categories
- boolean hasAllCategories = true;
- final ShareTargetInfo target = mShareTargets.get(j);
- for (int q = 0; q < target.mCategories.length; q++) {
- if (!categories.contains(target.mCategories[q])) {
- hasAllCategories = false;
+
+ // Get the list of all dynamic shortcuts in this package
+ final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
+ findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
+ ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+ int sharingShortcutCount = 0;
+ for (int i = 0; i < shortcuts.size(); i++) {
+ final Set<String> categories = shortcuts.get(i).getCategories();
+ if (categories == null || categories.isEmpty()) {
+ continue;
+ }
+ for (int j = 0; j < mShareTargets.size(); j++) {
+ // A SharingShortcut must have all of share target categories
+ boolean hasAllCategories = true;
+ final ShareTargetInfo target = mShareTargets.get(j);
+ for (int q = 0; q < target.mCategories.length; q++) {
+ if (!categories.contains(target.mCategories[q])) {
+ hasAllCategories = false;
+ break;
+ }
+ }
+ if (hasAllCategories) {
+ sharingShortcutCount++;
break;
}
}
- if (hasAllCategories) {
- sharingShortcutCount++;
- break;
- }
}
+ return sharingShortcutCount;
}
- return sharingShortcutCount;
}
/**
@@ -1090,19 +1106,25 @@
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
- try {
- newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
- getPackageName(), getPackageUserId(), mShareTargets);
- } catch (IOException|XmlPullParserException e) {
- Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+ final int shareTargetSize;
+ synchronized (mLock) {
+ try {
+ shareTargetSize = mShareTargets.size();
+ newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
+ getPackageName(), getPackageUserId(), mShareTargets);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+ }
}
final int manifestShortcutSize = newManifestShortcutList == null ? 0
: newManifestShortcutList.size();
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
Slog.d(TAG,
- String.format("Package %s has %d manifest shortcut(s), and %d share target(s)",
- getPackageName(), manifestShortcutSize, mShareTargets.size()));
+ String.format(
+ "Package %s has %d manifest shortcut(s), and %d share target(s)",
+ getPackageName(), manifestShortcutSize, shareTargetSize));
}
+
if (isNewApp && (manifestShortcutSize == 0)) {
// If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
@@ -1701,37 +1723,38 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = mShortcuts.size();
- final int shareTargetSize = mShareTargets.size();
+ synchronized (mLock) {
+ final int size = mShortcuts.size();
+ final int shareTargetSize = mShareTargets.size();
- if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) {
- return; // nothing to write.
- }
+ if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) {
+ return; // nothing to write.
+ }
- out.startTag(null, TAG_ROOT);
+ out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
- ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
- ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
- if (!forBackup) {
- synchronized (mLock) {
- ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsAppSearchSchemaUpToDate)
+ ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
+ ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
+ ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+ if (!forBackup) {
+ ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, mIsAppSearchSchemaUpToDate
? AppSearchShortcutInfo.SCHEMA_VERSION : 0);
}
- }
- getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
+ getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
- }
-
- if (!forBackup) {
- for (int j = 0; j < shareTargetSize; j++) {
- mShareTargets.get(j).saveToXml(out);
+ for (int j = 0; j < size; j++) {
+ saveShortcut(
+ out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
}
- }
- out.endTag(null, TAG_ROOT);
+ if (!forBackup) {
+ for (int j = 0; j < shareTargetSize; j++) {
+ mShareTargets.get(j).saveToXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_ROOT);
+ }
}
private void saveShortcut(TypedXmlSerializer out, ShortcutInfo si, boolean forBackup,
@@ -1917,38 +1940,38 @@
synchronized (ret.mLock) {
ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute(
parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION;
- }
- ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
- ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+ ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
+ ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- if (depth == outerDepth + 1) {
- switch (tag) {
- case ShortcutPackageInfo.TAG_ROOT:
- ret.getPackageInfo().loadFromXml(parser, fromBackup);
-
- continue;
- case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName,
- shortcutUser.getUserId(), fromBackup);
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
- continue;
- case TAG_SHARE_TARGET:
- ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
- continue;
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
}
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case ShortcutPackageInfo.TAG_ROOT:
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
+ continue;
+ case TAG_SHORTCUT:
+ final ShortcutInfo si = parseShortcut(parser, packageName,
+ shortcutUser.getUserId(), fromBackup);
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ continue;
+ case TAG_SHARE_TARGET:
+ ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
+ continue;
+ }
+ }
+ ShortcutService.warnForInvalidTag(depth, tag);
}
- ShortcutService.warnForInvalidTag(depth, tag);
}
return ret;
}
@@ -2152,7 +2175,9 @@
@VisibleForTesting
List<ShareTargetInfo> getAllShareTargetsForTest() {
- return new ArrayList<>(mShareTargets);
+ synchronized (mLock) {
+ return new ArrayList<>(mShareTargets);
+ }
}
@Override
@@ -2291,15 +2316,19 @@
private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
- for (ShortcutInfo si : shortcuts) {
- mShortcuts.put(si.getId(), si);
+ synchronized (mLock) {
+ for (ShortcutInfo si : shortcuts) {
+ mShortcuts.put(si.getId(), si);
+ }
}
}
@Nullable
List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) {
- return ids.stream().map(mShortcuts::get)
- .filter(Objects::nonNull).collect(Collectors.toList());
+ synchronized (mLock) {
+ return ids.stream().map(mShortcuts::get)
+ .filter(Objects::nonNull).collect(Collectors.toList());
+ }
}
private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) {
@@ -2318,10 +2347,12 @@
private void forEachShortcutStopWhen(
@NonNull final Function<ShortcutInfo, Boolean> cb) {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- if (cb.apply(si)) {
- return;
+ synchronized (mLock) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (cb.apply(si)) {
+ return;
+ }
}
}
}
@@ -2461,6 +2492,7 @@
})));
}
+ @GuardedBy("mLock")
@Override
void scheduleSaveToAppSearchLocked() {
final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts);
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 95482d7..974a1e1 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -70,9 +70,16 @@
void prepareUserData(int userId, int userSerial, int flags) {
synchronized (mInstallLock) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ /*
+ * Internal storage must be prepared before adoptable storage, since the user's volume
+ * keys are stored in their internal storage.
+ */
+ prepareUserDataLI(null /* internal storage */, userId, userSerial, flags, true);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
- prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+ if (volumeUuid != null) {
+ prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+ }
}
}
}
@@ -136,10 +143,17 @@
void destroyUserData(int userId, int flags) {
synchronized (mInstallLock) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ /*
+ * Volume destruction order isn't really important, but to avoid any weird issues we
+ * process internal storage last, the opposite of prepareUserData.
+ */
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
- destroyUserDataLI(volumeUuid, userId, flags);
+ if (volumeUuid != null) {
+ destroyUserDataLI(volumeUuid, userId, flags);
+ }
}
+ destroyUserDataLI(null /* internal storage */, userId, flags);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index a04f6d6..f727c11 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1018,8 +1018,6 @@
for (String packageName : packageNames) {
grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId,
PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS);
- grantPermissionsToPackage(NO_PM_CACHE, packageName, userId, false, false,
- NOTIFICATION_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java
index 446e20b7..6c2354f 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java
@@ -17,6 +17,7 @@
package com.android.server.pm.permission;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
/**
@@ -105,6 +106,20 @@
void scheduleReadDefaultPermissionExceptions();
/**
+ * Check whether a particular package should have access to the microphone data from the
+ * SoundTrigger.
+ *
+ * @param uid the uid of the package you are checking against
+ * @param packageName the name of the package you are checking against
+ * @param attributionTag the attributionTag to attach to the app op transaction
+ * @param reason the reason to attach to the app op transaction
+ * @return {@code PERMISSION_GRANTED} if the permission is granted,
+ * or {@code PERMISSION_SOFT/HARD DENIED otherwise
+ */
+ int checkSoundTriggerRecordAudioPermissionForDataDelivery(int uid,
+ @NonNull String packageName, @Nullable String attributionTag, @NonNull String reason);
+
+ /**
* Provider for package names.
*/
interface PackagesProvider {
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index 360a04f..88b4a94 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -16,12 +16,15 @@
package com.android.server.pm.permission;
+import static android.Manifest.permission.RECORD_AUDIO;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -399,6 +402,21 @@
public void scheduleReadDefaultPermissionExceptions() {
mDefaultPermissionGrantPolicy.scheduleReadDefaultPermissionExceptions();
}
+
+ @Override
+ public int checkSoundTriggerRecordAudioPermissionForDataDelivery(int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @NonNull String reason) {
+ int result = PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO, -1,
+ uid, packageName);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ return result;
+ }
+ mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(
+ AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, uid, packageName,
+ attributionTag, reason);
+ return result;
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index e1ff9ea..6ee9c66 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -392,6 +392,9 @@
if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
liteParseFlags = flags;
}
+ if ((flags & PARSE_APK_IN_APEX) != 0) {
+ liteParseFlags |= PARSE_APK_IN_APEX;
+ }
final ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
liteParseFlags);
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index e157a27..a6d148c 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -87,8 +87,8 @@
private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;
/**
- * Whether this device allows only the HotwordDetectionService to use OP_RECORD_AUDIO_HOTWORD
- * which doesn't incur the privacy indicator.
+ * Whether this device allows only the HotwordDetectionService to use
+ * OP_RECORD_AUDIO_HOTWORD which doesn't incur the privacy indicator.
*/
private final boolean mIsHotwordDetectionServiceRequired;
@@ -428,8 +428,8 @@
if (!mIsHotwordDetectionServiceRequired) {
return code;
}
- // Only the HotwordDetectionService can use the HOTWORD op which doesn't incur the
- // privacy indicator. Downgrade to standard RECORD_AUDIO for other processes.
+ // Only the HotwordDetectionService can use the RECORD_AUDIO_HOTWORD op which doesn't
+ // incur the privacy indicator. Downgrade to standard RECORD_AUDIO for other processes.
final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
if (hotwordDetectionServiceIdentity != null
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 7ba1cad..977f79f 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1453,16 +1453,6 @@
}
}
- try {
- if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, UserHandle.USER_SYSTEM)
- == 0) {
- return false;
- }
- } catch (Settings.SettingNotFoundException e) {
- return false;
- }
-
if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
|| CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
|| mKeyguardManager.isKeyguardLocked()) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b142141..d8e7fbe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -27,7 +27,6 @@
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -130,7 +129,6 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.session.MediaSessionLegacyHelper;
-import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeviceIdleManager;
@@ -396,7 +394,6 @@
PowerManagerInternal mPowerManagerInternal;
IStatusBarService mStatusBarService;
StatusBarManagerInternal mStatusBarManagerInternal;
- BatteryManagerInternal mBatteryManagerInternal;
AudioManagerInternal mAudioManagerInternal;
DisplayManager mDisplayManager;
DisplayManagerInternal mDisplayManagerInternal;
@@ -791,8 +788,7 @@
@Override
public void onWakeUp() {
synchronized (mLock) {
- if (shouldEnableWakeGestureLp()
- && getBatteryManagerInternal().getPlugType() != BATTERY_PLUGGED_WIRELESS) {
+ if (shouldEnableWakeGestureLp()) {
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
"Wake Up");
wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture,
@@ -849,15 +845,6 @@
}
}
- BatteryManagerInternal getBatteryManagerInternal() {
- synchronized (mServiceAcquireLock) {
- if (mBatteryManagerInternal == null) {
- mBatteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- }
- return mBatteryManagerInternal;
- }
- }
// returns true if the key was handled and should not be passed to the user
private boolean backKeyPress() {
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index 750d400..fd6ec06 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -16,44 +16,105 @@
package com.android.server.sensorprivacy;
+import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
+
+import android.annotation.ColorInt;
import android.app.AppOpsManager;
import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.SystemClock;
import android.permission.PermissionManager;
import android.util.ArraySet;
+import android.util.Pair;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
-class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener {
+class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
+ SensorEventListener {
+ @VisibleForTesting
+ static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
+
+ private final Handler mHandler;
+ private final Executor mExecutor;
private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
private final LightsManager mLightsManager;
+ private final SensorManager mSensorManager;
private final Set<String> mActivePackages = new ArraySet<>();
private final Set<String> mActivePhonePackages = new ArraySet<>();
- private final int mCameraPrivacyLightColor;
-
private final List<Light> mCameraLights = new ArrayList<>();
- private final AppOpsManager mAppOpsManager;
private LightsManager.LightsSession mLightsSession = null;
+ @ColorInt
+ private final int mDayColor;
+ @ColorInt
+ private final int mNightColor;
+
+ private final Sensor mLightSensor;
+
+ private boolean mIsAmbientLightListenerRegistered = false;
+ private final long mMovingAverageIntervalMillis;
+ /** When average of the time integral over the past {@link #mMovingAverageIntervalMillis}
+ * milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness
+ * else use nighttime brightness. */
+ private final long mNightThreshold;
+ private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>();
+ /** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is
+ * needed */
+ private long mAlvSum = 0;
+ private int mLastLightColor = 0;
+ /** The elapsed real time that the ALS was started watching */
+ private long mElapsedTimeStartedReading;
+
+ private final Object mDelayedUpdateToken = new Object();
+
+ // Can't mock static native methods, workaround for testing
+ private long mElapsedRealTime = -1;
+
CameraPrivacyLightController(Context context) {
+ this(context, FgThread.get().getLooper());
+ }
+
+ @VisibleForTesting
+ CameraPrivacyLightController(Context context, Looper looper) {
mContext = context;
+ mHandler = new Handler(looper);
+ mExecutor = new HandlerExecutor(mHandler);
+
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mLightsManager = mContext.getSystemService(LightsManager.class);
+ mSensorManager = mContext.getSystemService(SensorManager.class);
- mCameraPrivacyLightColor = mContext.getColor(R.color.camera_privacy_light);
+ mDayColor = mContext.getColor(R.color.camera_privacy_light_day);
+ mNightColor = mContext.getColor(R.color.camera_privacy_light_night);
+ mMovingAverageIntervalMillis = mContext.getResources()
+ .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
+ mNightThreshold = (long) (Math.log(mContext.getResources()
+ .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold))
+ * LIGHT_VALUE_MULTIPLIER);
List<Light> lights = mLightsManager.getLights();
for (int i = 0; i < lights.size(); i++) {
@@ -64,12 +125,60 @@
}
if (mCameraLights.isEmpty()) {
+ mLightSensor = null;
return;
}
mAppOpsManager.startWatchingActive(
new String[] {AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_PHONE_CALL_CAMERA},
- FgThread.getExecutor(), this);
+ mExecutor, this);
+
+ // It may be useful in the future to configure devices to know which lights are near which
+ // sensors so that we can control individual lights based on their environment.
+ mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ }
+
+ private void addElement(long time, int value) {
+ if (mAmbientLightValues.isEmpty()) {
+ // Eliminate the size == 1 edge case and assume the light value has been constant for
+ // the previous interval
+ mAmbientLightValues.add(new Pair<>(time - getCurrentIntervalMillis() - 1, value));
+ }
+ Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast();
+ mAmbientLightValues.add(new Pair<>(time, value));
+
+ mAlvSum += (time - lastElement.first) * lastElement.second;
+ removeObsoleteData(time);
+ }
+
+ private void removeObsoleteData(long time) {
+ while (mAmbientLightValues.size() > 1) {
+ Pair<Long, Integer> element0 = mAmbientLightValues.pollFirst(); // NOTICE: POLL
+ Pair<Long, Integer> element1 = mAmbientLightValues.peekFirst(); // NOTICE: PEEK
+ if (element1.first > time - getCurrentIntervalMillis()) {
+ mAmbientLightValues.addFirst(element0);
+ break;
+ }
+ mAlvSum -= (element1.first - element0.first) * element0.second;
+ }
+ }
+
+ /**
+ * Gives the Riemann sum of {@link #mAmbientLightValues} where the part of the interval that
+ * stretches outside the time window is removed and the time since the last change is added in.
+ */
+ private long getLiveAmbientLightTotal() {
+ if (mAmbientLightValues.isEmpty()) {
+ return mAlvSum;
+ }
+ long time = getElapsedRealTime();
+ removeObsoleteData(time);
+
+ Pair<Long, Integer> firstElement = mAmbientLightValues.peekFirst();
+ Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast();
+
+ return mAlvSum - Math.max(0, time - getCurrentIntervalMillis() - firstElement.first)
+ * firstElement.second + (time - lastElement.first) * lastElement.second;
}
@Override
@@ -93,10 +202,16 @@
}
private void updateLightSession() {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ mHandler.post(this::updateLightSession);
+ return;
+ }
+
Set<String> exemptedPackages = PermissionManager.getIndicatorExemptedPackages(mContext);
boolean shouldSessionEnd = exemptedPackages.containsAll(mActivePackages)
&& exemptedPackages.containsAll(mActivePhonePackages);
+ updateSensorListener(shouldSessionEnd);
if (shouldSessionEnd) {
if (mLightsSession == null) {
@@ -106,20 +221,73 @@
mLightsSession.close();
mLightsSession = null;
} else {
- if (mLightsSession != null) {
+ int lightColor;
+ if (mLightSensor != null && getLiveAmbientLightTotal()
+ < getCurrentIntervalMillis() * mNightThreshold) {
+ lightColor = mNightColor;
+ } else {
+ lightColor = mDayColor;
+ }
+
+ if (mLastLightColor == lightColor && mLightsSession != null) {
return;
}
+ mLastLightColor = lightColor;
LightsRequest.Builder requestBuilder = new LightsRequest.Builder();
for (int i = 0; i < mCameraLights.size(); i++) {
requestBuilder.addLight(mCameraLights.get(i),
new LightState.Builder()
- .setColor(mCameraPrivacyLightColor)
+ .setColor(lightColor)
.build());
}
- mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE);
+ if (mLightsSession == null) {
+ mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE);
+ }
+
mLightsSession.requestLights(requestBuilder.build());
}
}
+
+ private void updateSensorListener(boolean shouldSessionEnd) {
+ if (shouldSessionEnd && mIsAmbientLightListenerRegistered) {
+ mSensorManager.unregisterListener(this);
+ mIsAmbientLightListenerRegistered = false;
+ }
+ if (!shouldSessionEnd && !mIsAmbientLightListenerRegistered && mLightSensor != null) {
+ mSensorManager.registerListener(this, mLightSensor, SENSOR_DELAY_NORMAL, mHandler);
+ mIsAmbientLightListenerRegistered = true;
+ mElapsedTimeStartedReading = getElapsedRealTime();
+ }
+ }
+
+ private long getElapsedRealTime() {
+ return mElapsedRealTime == -1 ? SystemClock.elapsedRealtime() : mElapsedRealTime;
+ }
+
+ @VisibleForTesting
+ void setElapsedRealTime(long time) {
+ mElapsedRealTime = time;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // Using log space to represent human sensation (Fechner's Law) instead of lux
+ // because lux values causes bright flashes to skew the average very high.
+ addElement(event.timestamp, Math.max(0,
+ (int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER)));
+ updateLightSession();
+ mHandler.removeCallbacksAndMessages(mDelayedUpdateToken);
+ mHandler.postDelayed(CameraPrivacyLightController.this::updateLightSession,
+ mDelayedUpdateToken, mMovingAverageIntervalMillis);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+ private long getCurrentIntervalMillis() {
+ return Math.min(mMovingAverageIntervalMillis,
+ getElapsedRealTime() - mElapsedTimeStartedReading);
+ }
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 3c779f3..c354f11 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -25,6 +25,7 @@
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -1067,6 +1068,10 @@
mAppOpsRestrictionToken);
mAppOpsManagerInternal.setGlobalRestriction(OP_PHONE_CALL_MICROPHONE, enabled,
mAppOpsRestrictionToken);
+ // We don't show the dialog for RECEIVE_SOUNDTRIGGER_AUDIO, but still want to
+ // restrict it when the microphone is disabled
+ mAppOpsManagerInternal.setGlobalRestriction(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+ enabled, mAppOpsRestrictionToken);
break;
case CAMERA:
mAppOpsManagerInternal.setGlobalRestriction(OP_CAMERA, enabled,
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index f3d151f..13fe14c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -41,6 +41,9 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+
import java.io.PrintWriter;
import java.util.Objects;
@@ -120,8 +123,7 @@
* Throws a {@link SecurityException} iff the originator has permission to receive data.
*/
void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) {
- enforcePermissionForDataDelivery(mContext, identity, RECORD_AUDIO,
- reason);
+ enforceSoundTriggerRecordAudioPermissionForDataDelivery(identity, reason);
enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD,
reason);
}
@@ -147,6 +149,19 @@
}
}
+ private static void enforceSoundTriggerRecordAudioPermissionForDataDelivery(
+ @NonNull Identity identity, @NonNull String reason) {
+ LegacyPermissionManagerInternal lpmi =
+ LocalServices.getService(LegacyPermissionManagerInternal.class);
+ final int status = lpmi.checkSoundTriggerRecordAudioPermissionForDataDelivery(identity.uid,
+ identity.packageName, identity.attributionTag, reason);
+ if (status != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ String.format("Failed to obtain permission RECORD_AUDIO for identity %s",
+ ObjectPrinter.print(identity, 16)));
+ }
+ }
+
/**
* Throws a {@link SecurityException} if originator permanently doesn't have the given
* permission.
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 5e7b586..977f6fd 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -204,6 +204,7 @@
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.health.HealthServiceWrapper;
import com.android.server.notification.NotificationManagerService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.stats.pull.netstats.NetworkStatsExt;
@@ -4106,11 +4107,24 @@
// Incremental is not enabled on this device. The result list will be empty.
return StatsManager.PULL_SUCCESS;
}
- List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
- for (PackageInfo pi : installedPackages) {
- if (IncrementalManager.isIncrementalPath(pi.applicationInfo.getBaseCodePath())) {
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+ for (int userId : userIds) {
+ List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(0, userId);
+ for (PackageInfo pi : installedPackages) {
+ if (IncrementalManager.isIncrementalPath(
+ pi.applicationInfo.getBaseCodePath())) {
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid));
+ }
+ }
}
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to pullInstalledIncrementalPackagesLocked", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
return StatsManager.PULL_SUCCESS;
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index d374814..4b8c7c1 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -122,16 +122,9 @@
if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
return;
}
- if (!mWaitingForTrustableDowngrade) {
- return;
- }
// are these the broadcasts we want to listen to
- if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
- || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
- mTrusted = false;
- mTrustable = true;
- mWaitingForTrustableDowngrade = false;
- mTrustManagerService.updateTrust(mUserId, 0);
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ downgradeToTrustable();
}
}
};
@@ -480,8 +473,7 @@
final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
- IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
- trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
// Schedules a restart for when connecting times out. If the connection succeeds,
// the restart is canceled in mCallback's onConnected.
@@ -668,6 +660,19 @@
mTrustable = false;
}
+ /**
+ * Downgrades the trustagent to trustable as a result of a keyguard or screen related event, and
+ * then updates the trust state of the phone to reflect the change.
+ */
+ public void downgradeToTrustable() {
+ if (mWaitingForTrustableDowngrade) {
+ mWaitingForTrustableDowngrade = false;
+ mTrusted = false;
+ mTrustable = true;
+ mTrustManagerService.updateTrust(mUserId, 0);
+ }
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8f4ddea..80ce70d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1184,6 +1184,22 @@
return false;
}
+ /**
+ * We downgrade to trustable whenever keyguard changes its showing value.
+ * - becomes showing: something has caused the device to show keyguard which happens due to
+ * user intent to lock the device either through direct action or a timeout
+ * - becomes not showing: keyguard was dismissed and we no longer need to keep the device
+ * unlocked
+ * */
+ private void dispatchTrustableDowngrade() {
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == mCurrentUser) {
+ info.agent.downgradeToTrustable();
+ }
+ }
+ }
+
private List<String> getTrustGrantedMessages(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return new ArrayList<>();
@@ -1752,6 +1768,7 @@
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
case MSG_KEYGUARD_SHOWING_CHANGED:
+ dispatchTrustableDowngrade();
refreshDeviceLockedForUser(mCurrentUser);
break;
case MSG_START_USER:
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index ee30fa2..fb9a4d4 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -15,6 +15,8 @@
*/
package com.android.server.tv.tunerresourcemanager;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
+
import java.util.HashSet;
import java.util.Set;
@@ -63,6 +65,11 @@
private int mNiceValue;
/**
+ * The handle of the primary frontend resource
+ */
+ private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+
+ /**
* List of the frontend handles that are used by the current client.
*/
private Set<Integer> mUsingFrontendHandles = new HashSet<>();
@@ -175,6 +182,22 @@
}
/**
+ * Set the primary frontend used by the client
+ *
+ * @param frontendHandle being used.
+ */
+ public void setPrimaryFrontend(int frontendHandle) {
+ mPrimaryUsingFrontendHandle = frontendHandle;
+ }
+
+ /**
+ * Get the primary frontend used by the client
+ */
+ public int getPrimaryFrontend() {
+ return mPrimaryUsingFrontendHandle;
+ }
+
+ /**
* Update the set of client that share frontend with the current client.
*
* @param clientId the client to share the fe with the current client.
@@ -206,6 +229,7 @@
public void releaseFrontend() {
mUsingFrontendHandles.clear();
mShareFeClientIds.clear();
+ mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
}
/**
@@ -276,6 +300,7 @@
public void reclaimAllResources() {
mUsingFrontendHandles.clear();
mShareFeClientIds.clear();
+ mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
mUsingLnbHandles.clear();
mUsingCasSystemId = INVALID_RESOURCE_ID;
mUsingCiCamId = INVALID_RESOURCE_ID;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index af705d5..6162d716 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -42,6 +42,7 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -72,14 +73,28 @@
private static final long INVALID_THREAD_ID = -1;
private static final long TRMS_LOCK_TIMEOUT = 500;
+ private static final int INVALID_FE_COUNT = -1;
+
// Map of the registered client profiles
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
private int mNextUnusedClientId = 0;
// Map of the current available frontend resources
private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
- // Backup Map of the current available frontend resources
+ // SparseIntArray of the max usable number for each frontend resource type
+ private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray();
+ // SparseIntArray of the currently used number for each frontend resource type
+ private SparseIntArray mFrontendUsedNums = new SparseIntArray();
+ // SparseIntArray of the existing number for each frontend resource type
+ private SparseIntArray mFrontendExistingNums = new SparseIntArray();
+
+ // Backups for the frontend resource maps for enabling testing with custom resource maps
+ // such as TunerTest.testHasUnusedFrontend1()
private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
+ private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray();
+ private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
+ private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
+
// Map of the current available lnb resources
private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
// Map of the current available Cas resources
@@ -268,6 +283,29 @@
}
@Override
+ public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) {
+ enforceTunerAccessPermission("requestFrontend");
+ enforceTrmAccessPermission("requestFrontend");
+ if (maxUsableNum < 0) {
+ Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum
+ + " frontendType:" + frontendType);
+ return false;
+ }
+ synchronized (mLock) {
+ return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum);
+ }
+ }
+
+ @Override
+ public int getMaxNumberOfFrontends(int frontendType) {
+ enforceTunerAccessPermission("requestFrontend");
+ enforceTrmAccessPermission("requestFrontend");
+ synchronized (mLock) {
+ return getMaxNumberOfFrontendsInternal(frontendType);
+ }
+ }
+
+ @Override
public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
enforceTunerAccessPermission("shareFrontend");
enforceTrmAccessPermission("shareFrontend");
@@ -572,71 +610,19 @@
}
synchronized (mLock) {
- if (mClientProfiles != null) {
- pw.println("ClientProfiles:");
- pw.increaseIndent();
- for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mFrontendResources != null) {
- pw.println("FrontendResources:");
- pw.increaseIndent();
- for (Map.Entry<Integer, FrontendResource> entry
- : mFrontendResources.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mFrontendResourcesBackup != null) {
- pw.println("FrontendResourcesBackUp:");
- pw.increaseIndent();
- for (Map.Entry<Integer, FrontendResource> entry
- : mFrontendResourcesBackup.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mLnbResources != null) {
- pw.println("LnbResources:");
- pw.increaseIndent();
- for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mCasResources != null) {
- pw.println("CasResources:");
- pw.increaseIndent();
- for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mCiCamResources != null) {
- pw.println("CiCamResources:");
- pw.increaseIndent();
- for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
-
- if (mListeners != null) {
- pw.println("Listners:");
- pw.increaseIndent();
- for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry
- : mListeners.entrySet()) {
- pw.println(entry.getKey() + " : " + entry.getValue());
- }
- pw.decreaseIndent();
- }
+ dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw);
+ dumpMap(mFrontendResources, "FrontendResources:", "\n", pw);
+ dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw);
+ dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw);
+ dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw);
+ dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw);
+ dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw);
+ dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
+ dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
+ dumpMap(mLnbResources, "LnbResource:", "\n", pw);
+ dumpMap(mCasResources, "CasResource:", "\n", pw);
+ dumpMap(mCiCamResources, "CiCamResource:", "\n", pw);
+ dumpMap(mListeners, "Listners:", "\n", pw);
}
}
@@ -786,10 +772,10 @@
protected void storeResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
- if (mFrontendResources != null && mFrontendResources.size() > 0) {
- mFrontendResourcesBackup.putAll(mFrontendResources);
- mFrontendResources.clear();
- }
+ replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup);
+ replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup);
+ replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup);
+ replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup);
break;
// TODO: implement for other resource type when needed
default:
@@ -800,9 +786,10 @@
protected void clearResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
- if (mFrontendResources != null) {
- mFrontendResources.clear();
- }
+ replaceFeResourceMap(null, mFrontendResources);
+ replaceFeCounts(null, mFrontendExistingNums);
+ replaceFeCounts(null, mFrontendUsedNums);
+ replaceFeCounts(null, mFrontendMaxUsableNums);
break;
// TODO: implement for other resource type when needed
default:
@@ -813,12 +800,10 @@
protected void restoreResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
- if (mFrontendResourcesBackup != null
- && mFrontendResourcesBackup.size() > 0) {
- mFrontendResources.clear();
- mFrontendResources.putAll(mFrontendResourcesBackup);
- mFrontendResourcesBackup.clear();
- }
+ replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources);
+ replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums);
+ replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums);
+ replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums);
break;
// TODO: implement for other resource type when needed
default:
@@ -954,6 +939,11 @@
for (FrontendResource fr : getFrontendResources().values()) {
if (fr.getType() == request.frontendType) {
if (!fr.isInUse()) {
+ // Unused resource cannot be acquired if the max is already reached, but
+ // TRM still has to look for the reclaim candidate
+ if (isFrontendMaxNumUseReached(request.frontendType)) {
+ continue;
+ }
// Grant unused frontend with no exclusive group members first.
if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
grantingFrontendHandle = fr.getHandle();
@@ -1021,6 +1011,9 @@
for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
getFrontendResource(inUseHandle).setOwner(newOwnerId);
}
+ // change the primary frontend
+ newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend());
+ currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE);
// double check there is no other resources tied to the previous owner
for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
@@ -1657,11 +1650,13 @@
FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
ClientProfile ownerProfile = getClientProfile(ownerClientId);
grantingFrontend.setOwner(ownerClientId);
+ increFrontendNum(mFrontendUsedNums, grantingFrontend.getType());
ownerProfile.useFrontend(grantingHandle);
for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
ownerProfile.useFrontend(exclusiveGroupMember);
}
+ ownerProfile.setPrimaryFrontend(grantingHandle);
}
private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
@@ -1755,6 +1750,109 @@
return mFrontendResources;
}
+ private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) {
+ int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
+ if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) {
+ mFrontendMaxUsableNums.put(frontendType, maxUsableNum);
+ return true;
+ } else {
+ Slog.e(TAG, "max number of frontend for frontendType: " + frontendType
+ + " cannot be set to a value lower than the current usage count."
+ + " (requested max num = " + maxUsableNum + ", current usage = " + usedNum);
+ return false;
+ }
+ }
+
+ private int getMaxNumberOfFrontendsInternal(int frontendType) {
+ int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT);
+ if (existingNum == INVALID_FE_COUNT) {
+ Log.e(TAG, "existingNum is -1 for " + frontendType);
+ return -1;
+ }
+ int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
+ if (maxUsableNum == INVALID_FE_COUNT) {
+ return existingNum;
+ } else {
+ return maxUsableNum;
+ }
+ }
+
+ private boolean isFrontendMaxNumUseReached(int frontendType) {
+ int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
+ if (maxUsableNum == INVALID_FE_COUNT) {
+ return false;
+ }
+ int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
+ if (useNum == INVALID_FE_COUNT) {
+ useNum = 0;
+ }
+ return useNum >= maxUsableNum;
+ }
+
+ private void increFrontendNum(SparseIntArray targetNums, int frontendType) {
+ int num = targetNums.get(frontendType, INVALID_FE_COUNT);
+ if (num == INVALID_FE_COUNT) {
+ targetNums.put(frontendType, 1);
+ } else {
+ targetNums.put(frontendType, num + 1);
+ }
+ }
+
+ private void decreFrontendNum(SparseIntArray targetNums, int frontendType) {
+ int num = targetNums.get(frontendType, INVALID_FE_COUNT);
+ if (num != INVALID_FE_COUNT) {
+ targetNums.put(frontendType, num - 1);
+ }
+ }
+
+ private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer,
+ FrontendResource> dstMap) {
+ if (dstMap != null) {
+ dstMap.clear();
+ if (srcMap != null && srcMap.size() > 0) {
+ dstMap.putAll(srcMap);
+ }
+ }
+ }
+
+ private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) {
+ if (dstCounts != null) {
+ dstCounts.clear();
+ if (srcCounts != null) {
+ for (int i = 0; i < srcCounts.size(); i++) {
+ dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i));
+ }
+ }
+ }
+ }
+ private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter,
+ IndentingPrintWriter pw) {
+ if (targetMap != null) {
+ pw.println(headline);
+ pw.increaseIndent();
+ for (Map.Entry<?, ?> entry : targetMap.entrySet()) {
+ pw.print(entry.getKey() + " : " + entry.getValue());
+ pw.print(delimiter);
+ }
+ pw.println();
+ pw.decreaseIndent();
+ }
+ }
+
+ private void dumpSIA(SparseIntArray array, String headline, String delimiter,
+ IndentingPrintWriter pw) {
+ if (array != null) {
+ pw.println(headline);
+ pw.increaseIndent();
+ for (int i = 0; i < array.size(); i++) {
+ pw.print(array.keyAt(i) + " : " + array.valueAt(i));
+ pw.print(delimiter);
+ }
+ pw.println();
+ pw.decreaseIndent();
+ }
+ }
+
private void addFrontendResource(FrontendResource newFe) {
// Update the exclusive group member list in all the existing Frontend resource
for (FrontendResource fe : getFrontendResources().values()) {
@@ -1771,6 +1869,8 @@
}
// Update resource list and available id list
mFrontendResources.put(newFe.getHandle(), newFe);
+ increFrontendNum(mFrontendExistingNums, newFe.getType());
+
}
private void removeFrontendResource(int removingHandle) {
@@ -1789,6 +1889,7 @@
getFrontendResource(excGroupmemberFeHandle)
.removeExclusiveGroupMemberFeId(fe.getHandle());
}
+ decreFrontendNum(mFrontendExistingNums, fe.getType());
mFrontendResources.remove(removingHandle);
}
@@ -1918,6 +2019,15 @@
}
}
+
+ int primaryFeId = profile.getPrimaryFrontend();
+ if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ FrontendResource primaryFe = getFrontendResource(primaryFeId);
+ if (primaryFe != null) {
+ decreFrontendNum(mFrontendUsedNums, primaryFe.getType());
+ }
+ }
+
profile.releaseFrontend();
}
diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java
index 6059f96..07474a63 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayList.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayList.java
@@ -416,7 +416,7 @@
dst.mStorage.ensureCapacity(end);
for (int i = 0; i < end; i++) {
final E val = Snapshots.maybeSnapshot(src.get(i));
- dst.add(i, val);
+ dst.mStorage.add(val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedArrayMap.java b/services/core/java/com/android/server/utils/WatchedArrayMap.java
index 7c1cde8..63441aa 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayMap.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayMap.java
@@ -465,7 +465,7 @@
for (int i = 0; i < end; i++) {
final V val = Snapshots.maybeSnapshot(src.valueAt(i));
final K key = src.keyAt(i);
- dst.put(key, val);
+ dst.mStorage.put(key, val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedArraySet.java b/services/core/java/com/android/server/utils/WatchedArraySet.java
index ec80261..a2eaed7 100644
--- a/services/core/java/com/android/server/utils/WatchedArraySet.java
+++ b/services/core/java/com/android/server/utils/WatchedArraySet.java
@@ -427,7 +427,7 @@
dst.mStorage.ensureCapacity(end);
for (int i = 0; i < end; i++) {
final E val = Snapshots.maybeSnapshot(src.valueAt(i));
- dst.append(val);
+ dst.mStorage.append(val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
index bf23de1..9da9e75 100644
--- a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
+++ b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
@@ -410,7 +410,7 @@
for (int i = 0; i < end; i++) {
final E val = Snapshots.maybeSnapshot(src.valueAt(i));
final long key = src.keyAt(i);
- dst.put(key, val);
+ dst.mStorage.put(key, val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseArray.java b/services/core/java/com/android/server/utils/WatchedSparseArray.java
index 9b99b91..6ce38b7 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseArray.java
@@ -490,7 +490,7 @@
for (int i = 0; i < end; i++) {
final E val = Snapshots.maybeSnapshot(src.valueAt(i));
final int key = src.keyAt(i);
- dst.put(key, val);
+ dst.mStorage.put(key, val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
index 772a8d0..50e2272 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
@@ -310,7 +310,7 @@
}
final int end = src.size();
for (int i = 0; i < end; i++) {
- dst.put(src.keyAt(i), src.valueAt(i));
+ dst.mStorage.put(src.keyAt(i), src.valueAt(i));
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java
index 72705bf..53d1682 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java
@@ -315,7 +315,7 @@
}
final int end = src.size();
for (int i = 0; i < end; i++) {
- dst.put(src.keyAt(i), src.valueAt(i));
+ dst.mStorage.put(src.keyAt(i), src.valueAt(i));
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
index 05db12e..77750ed 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -169,7 +169,7 @@
final ArraySet set = src.get(i);
final int setSize = set.size();
for (int j = 0; j < setSize; j++) {
- dst.add(src.keyAt(i), set.valueAt(j));
+ dst.mStorage.add(src.keyAt(i), set.valueAt(j));
}
}
dst.seal();
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 63619e5..f1dbad61 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -446,6 +446,43 @@
// Not relevant for the window observer.
}
+ public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ IBinder token) {
+ synchronized (mService.mGlobalLock) {
+ final Matrix transformationMatrix = new Matrix();
+ final MagnificationSpec magnificationSpec = new MagnificationSpec();
+
+ final WindowState windowState = mService.mWindowMap.get(token);
+ if (windowState != null) {
+ windowState.getTransformationMatrix(new float[9], transformationMatrix);
+
+ if (hasCallbacks()) {
+ final MagnificationSpec otherMagnificationSpec =
+ getMagnificationSpecForWindow(windowState);
+ if (otherMagnificationSpec != null && !otherMagnificationSpec.isNop()) {
+ magnificationSpec.setTo(otherMagnificationSpec);
+ }
+ }
+ }
+
+ return new Pair<>(transformationMatrix, magnificationSpec);
+ }
+ }
+
+ MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow",
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "windowState={" + windowState + "}");
+ }
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ return displayMagnifier.getMagnificationSpecForWindow(windowState);
+ }
+ return null;
+ }
+
boolean hasCallbacks() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
| FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 791d193..189fff8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3532,7 +3532,7 @@
final ActivityRecord next = getDisplayArea().topRunningActivity(
true /* considerKeyguardState */);
- // If the finishing activity is the last activity of a organized TaskFragment and has an
+ // If the finishing activity is the last activity of an organized TaskFragment and has an
// adjacent TaskFragment, check if the activity removal should be delayed.
boolean delayRemoval = false;
final TaskFragment taskFragment = getTaskFragment();
@@ -3540,7 +3540,8 @@
final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
final TaskFragment adjacent =
organized != null ? organized.getAdjacentTaskFragment() : null;
- if (adjacent != null && organized.topRunningActivity() == null) {
+ if (adjacent != null && next.isDescendantOf(adjacent)
+ && organized.topRunningActivity() == null) {
delayRemoval = organized.isDelayLastActivityRemoval();
}
}
@@ -5094,13 +5095,15 @@
// still check DC#okToAnimate again if the transition animation is fine to apply.
// TODO(new-app-transition): Rewrite this logic using WM Shell.
final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
+ final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
+ && mVisible == visible;
if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
&& (appTransition.isTransitionSet()
|| (recentsAnimating && !isActivityTypeHome()))
- // If the visibility change during enter PIP, we don't want to include it in app
- // transition to affect the animation theme, because the Pip organizer will animate
- // the entering PIP instead.
- && !mWaitForEnteringPinnedMode) {
+ // If the visibility is not changed during enter PIP, we don't want to include it in
+ // app transition to affect the animation theme, because the Pip organizer will
+ // animate the entering PIP instead.
+ && !isEnteringPipWithoutVisibleChange) {
if (visible) {
displayContent.mOpeningApps.add(this);
mEnteringAnimation = true;
@@ -6339,8 +6342,10 @@
mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(
- r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+ } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
+ // Don't block starting window removal if an Activity can't be a starting window
+ // target.
+ && r.mSharedStartingData != null) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -7983,20 +7988,7 @@
// orientation with insets applied.
return;
}
- // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app
- // compatibility testing showed that android:supportsPictureInPicture="true" alone is not
- // sufficient signal for not letterboxing an app.
- // TODO(214602463): Remove multi-window check since orientation and aspect ratio
- // restrictions should always be applied in multi-window.
- final boolean isResizeable = task != null
- // Activity should be resizable if the task is.
- ? task.isResizeable(/* checkPictureInPictureSupport */ false)
- || isResizeable(/* checkPictureInPictureSupport */ false)
- : isResizeable(/* checkPictureInPictureSupport */ false);
- if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) {
- // Ignore orientation request for resizable apps in multi window.
- return;
- }
+
if (windowingMode == WINDOWING_MODE_PINNED) {
// PiP bounds have higher priority than the requested orientation. Otherwise the
// activity may be squeezed into a small piece.
@@ -9338,6 +9330,7 @@
sb.append(mUserId);
sb.append(' ');
sb.append(intent.getComponent().flattenToShortString());
+ sb.append("}");
stringName = sb.toString();
return stringName;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 72408b6..d60981f 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -518,6 +518,8 @@
.setRequestCode(-1)
.setCallingUid(callingUid)
.setCallingPid(callingPid)
+ .setRealCallingUid(callingUid)
+ .setRealCallingPid(callingPid)
.setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId())
.execute();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b97ee7e..b2f27eb 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2732,8 +2732,8 @@
intentActivity.getTaskFragment().clearLastPausedActivity();
Task intentTask = intentActivity.getTask();
- // Only update the target-root-task when it is not indicated.
if (mTargetRootTask == null) {
+ // Update launch target task when it is not indicated.
if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) {
// Inherit the target-root-task from source to ensure trampoline activities will be
// launched into the same root task.
@@ -2742,6 +2742,17 @@
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask,
mOptions);
}
+ } else {
+ // If a launch target indicated, and the matching task is already in the adjacent task
+ // of the launch target. Adjust to use the adjacent task as its launch target. So the
+ // existing task will be launched into the closer one and won't be reparent redundantly.
+ // TODO(b/231541706): Migrate the logic to wm-shell after having proper APIs to help
+ // resolve target task without actually starting the activity.
+ final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null
+ ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null;
+ if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) {
+ mTargetRootTask = adjacentTargetTask;
+ }
}
// If the target task is not in the front, then we need to bring it to the front...
@@ -2771,7 +2782,7 @@
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
- if (mTargetRootTask == intentActivity.getRootTask()) {
+ if (intentActivity.isDescendantOf(mTargetRootTask)) {
// TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
// tasks hierarchies.
if (mTargetRootTask != intentTask
@@ -2818,19 +2829,6 @@
mTargetRootTask = intentActivity.getRootTask();
mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
-
- // We need to check if there is a launch root task in TDA for this target root task.
- // If it exist, we need to reparent target root task from TDA to launch root task.
- final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
- final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
- mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask,
- mLaunchFlags);
- // If target root task is created by organizer, let organizer handle reparent itself.
- if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
- && launchRootTask != mTargetRootTask) {
- mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
- mTargetRootTask = launchRootTask;
- }
}
private void resumeTargetRootTaskIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index ee7d9a9..46ce433 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -153,10 +153,10 @@
for (WindowContainer wc : mRootMembers) {
wc.waitForSyncTransactionCommit(wcAwaitingCommit);
}
- final Runnable callback = new Runnable() {
+ class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
- public void run() {
+ public void onCommitted() {
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
@@ -171,8 +171,23 @@
wcAwaitingCommit.clear();
}
}
+
+ // Called in timeout
+ @Override
+ public void run() {
+ // Sometimes we get a trace, sometimes we get a bugreport without
+ // a trace. Since these kind of ANRs can trigger such an issue,
+ // try and ensure we will have some visibility in both cases.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
+ Slog.e(TAG, "WM sent Transaction to organized, but never received" +
+ " commit callback. Application ANR likely to follow.");
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ onCommitted();
+
+ }
};
- merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::run);
+ CommitCallback callback = new CommitCallback();
+ merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b37f980..247117e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -124,8 +124,7 @@
LocalServices.getService(WindowManagerInternal.class);
IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken();
- window = wmService.windowForClientLocked(null, focusedWindowToken,
- false /* throwOnError */);
+ window = wmService.getFocusedWindowLocked();
if (window == null) {
EmbeddedWindowController.EmbeddedWindow embeddedWindow =
@@ -147,12 +146,6 @@
}
if (window == null) {
- window = wmService.getFocusedWindowLocked();
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Focused window found using wmService.getFocusedWindowLocked()");
- }
-
- if (window == null) {
// We don't have any focused window, fallback ont the top currentTask of the focused
// display.
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
@@ -194,7 +187,6 @@
if (backType == BackNavigationInfo.TYPE_CALLBACK
|| currentActivity == null
|| currentTask == null
- || currentTask.getDisplayContent().getImeContainer().isVisible()
|| currentActivity.isActivityTypeHome()) {
return infoBuilder
.setType(backType)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fe5a07df..66eca99 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2922,7 +2922,7 @@
}
DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
- if (mDisplayPolicy == null) {
+ if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
return null;
}
return DisplayCutout.fromResourcesRectApproximation(
@@ -2931,7 +2931,7 @@
}
RoundedCorners loadRoundedCorners(int displayWidth, int displayHeight) {
- if (mDisplayPolicy == null) {
+ if (mDisplayPolicy == null || mInitialRoundedCorners == null) {
return null;
}
return RoundedCorners.fromResources(
@@ -5265,7 +5265,7 @@
void prepareAppTransition(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
- if (prepared && okToAnimate()) {
+ if (prepared && okToAnimate() && transit != TRANSIT_NONE) {
mSkipAppTransitionAnimation = false;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 62998cb..d62bcdf 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -651,17 +651,19 @@
mForceShowNavBarSettingsObserver = new ForceShowNavBarSettingsObserver(
mHandler, mContext);
- mForceShowNavBarSettingsObserver.setOnChangeRunnable(() -> {
- synchronized (mLock) {
- mForceShowNavigationBarEnabled =
- mForceShowNavBarSettingsObserver.isEnabled();
- updateSystemBarAttributes();
- }
- });
+ mForceShowNavBarSettingsObserver.setOnChangeRunnable(this::updateForceShowNavBarSettings);
mForceShowNavigationBarEnabled = mForceShowNavBarSettingsObserver.isEnabled();
mHandler.post(mForceShowNavBarSettingsObserver::register);
}
+ private void updateForceShowNavBarSettings() {
+ synchronized (mLock) {
+ mForceShowNavigationBarEnabled =
+ mForceShowNavBarSettingsObserver.isEnabled();
+ updateSystemBarAttributes();
+ }
+ }
+
/**
* Returns the first non-null alt bar window matching the given position.
*/
@@ -1801,6 +1803,7 @@
*/
public void switchUser() {
updateCurrentUserResources();
+ updateForceShowNavBarSettings();
}
/**
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 67dd89e..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -270,6 +270,22 @@
}
}
+ @Override
+ public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ mService.setMousePointerDisplayId(displayId);
+ if (displayId == Display.INVALID_DISPLAY) return;
+
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
+ + " that does not have a valid DisplayContent.");
+ return;
+ }
+ mService.restorePointerIconLocked(dc, x, y);
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 65062e5..ce416ad 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -416,8 +416,22 @@
final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;
if (focusToken == null) {
+ if (recentsAnimationInputConsumer != null
+ && recentsAnimationInputConsumer.mWindowHandle != null
+ && mInputFocus == recentsAnimationInputConsumer.mWindowHandle.token) {
+ // Avoid removing input focus from recentsAnimationInputConsumer.
+ // When the recents animation input consumer has the input focus,
+ // mInputFocus does not match to mDisplayContent.mCurrentFocus. Making it to be
+ // a special case, that do not remove the input focus from it when
+ // mDisplayContent.mCurrentFocus is null. This special case should be removed
+ // once recentAnimationInputConsumer is removed.
+ return;
+ }
// When an app is focused, but its window is not showing yet, remove the input focus
- // from the current window.
+ // from the current window. This enforces the input focus to match
+ // mDisplayContent.mCurrentFocus. However, if more special cases are discovered that
+ // the input focus and mDisplayContent.mCurrentFocus are expected to mismatch,
+ // the whole logic of how and when to revoke focus needs to be checked.
if (mDisplayContent.mFocusedApp != null && mInputFocus != null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "App %s is focused,"
+ " but the window is not ready. Start a transaction to remove focus from"
@@ -573,7 +587,7 @@
recentsAnimationController.getTargetAppDisplayArea();
if (targetDA != null) {
mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
- mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
+ mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2);
mAddRecentsAnimationInputConsumerHandle = false;
}
}
@@ -584,10 +598,14 @@
final Task rootTask = w.getTask().getRootTask();
mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop(
rootTask.getSurfaceControl());
+ final DisplayArea targetDA = rootTask.getDisplayArea();
// We set the layer to z=MAX-1 so that it's always on top.
- mPipInputConsumer.reparent(mInputTransaction, rootTask);
- mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
- mAddPipInputConsumerHandle = false;
+ if (targetDA != null) {
+ mPipInputConsumer.layout(mInputTransaction, rootTask.getBounds());
+ mPipInputConsumer.reparent(mInputTransaction, targetDA);
+ mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
+ mAddPipInputConsumerHandle = false;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c0dff14..0aab186 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1999,7 +1999,10 @@
final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask();
if (rootPinnedTask != null) {
transitionController.collect(rootPinnedTask);
- rootPinnedTask.dismissPip();
+ // The new ActivityRecord should replace the existing PiP, so it's more desirable
+ // that the old PiP disappears instead of turning to full-screen at the same time,
+ // as the Task#dismissPip is trying to do.
+ removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
}
// Set a transition to ensure that we don't immediately try and update the visibility
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 4068a97..c7f8a1e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -158,7 +158,7 @@
void continueStartingAnimations() {
synchronized (mLock) {
mAnimationStartDeferred = false;
- if (!mPendingAnimations.isEmpty()) {
+ if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
@@ -204,7 +204,7 @@
mPreProcessingAnimations.remove(animationLeash);
mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred) {
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
@@ -214,7 +214,7 @@
if (!requiresEdgeExtension) {
mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred) {
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
@@ -330,6 +330,14 @@
private void startAnimations(long frameTimeNanos) {
synchronized (mLock) {
+ if (!mPreProcessingAnimations.isEmpty()) {
+ // We only want to start running animations once all mPreProcessingAnimations have
+ // been processed to ensure preprocessed animations start in sync.
+ // NOTE: This means we might delay running animations that require preprocessing if
+ // new animations that also require preprocessing are requested before the previous
+ // ones have finished (see b/227449117).
+ return;
+ }
startPendingAnimationsLocked();
}
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
@@ -553,4 +561,4 @@
return mAnimationHandler;
}
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f8bc26a..c0d7d13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -23,17 +23,18 @@
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Pair;
import android.view.Display;
import android.view.IInputFilter;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IWindow;
import android.view.InputChannel;
-import android.view.InputWindowHandle;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -459,6 +460,17 @@
public abstract void getWindowFrame(IBinder token, Rect outBounds);
/**
+ * Get the transformation matrix and MagnificationSpec given its token.
+ *
+ * @param token The token.
+ * @return The pair of the transformation matrix and magnification spec.
+ */
+ // TODO (b/231663133): Long term solution for tracking window when the
+ // FLAG_RETRIEVE_INTERACTIVE_WINDOWS is unset.
+ public abstract Pair<Matrix, MagnificationSpec>
+ getWindowTransformationMatrixAndMagnificationSpec(IBinder token);
+
+ /**
* Opens the global actions dialog.
*/
public abstract void showGlobalActions();
@@ -849,24 +861,12 @@
public abstract SurfaceControl getHandwritingSurfaceForDisplay(int displayId);
/**
- * Replaces the touchable region of the provided input surface with the crop of the window with
- * the provided token. This method will associate the inputSurface with a copy of
- * the given inputWindowHandle, where the copy is configured using
- * {@link InputWindowHandle#replaceTouchableRegionWithCrop(SurfaceControl)} with the surface
- * of the provided windowToken.
+ * Returns {@code true} if the given point is within the window bounds of the given window.
*
- * This is a no-op if windowToken is not valid or the window is not found.
- *
- * This does not change any other properties of the inputSurface.
- *
- * This method exists to avoid leaking the window's SurfaceControl outside WindowManagerService.
- *
- * @param inputSurface The surface for which the touchable region should be set.
- * @param inputWindowHandle The {@link InputWindowHandle} for the input surface.
- * @param windowToken The window whose bounds should be used as the touchable region for the
- * inputSurface.
+ * @param windowToken the window whose bounds should be used for the hit test.
+ * @param displayX the x coordinate of the test point in the display's coordinate space.
+ * @param displayY the y coordinate of the test point in the display's coordinate space.
*/
- public abstract void replaceInputSurfaceTouchableRegionWithWindowCrop(
- @NonNull SurfaceControl inputSurface, @NonNull InputWindowHandle inputWindowHandle,
- @NonNull IBinder windowToken);
+ public abstract boolean isPointInsideWindow(
+ @NonNull IBinder windowToken, int displayId, float displayX, float displayY);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2be8802..7a54804 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -173,6 +173,7 @@
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Bitmap;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -224,6 +225,7 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.MergedConfiguration;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
@@ -3058,10 +3060,10 @@
mRecentsAnimationController = null;
controller.cleanupAnimation(reorderMode);
// TODO(multi-display): currently only default display support recents animation.
- // Cancel any existing app transition animation running in the legacy transition
- // framework.
final DisplayContent dc = getDefaultDisplayContentLocked();
- dc.mAppTransition.freeze();
+ if (dc.mAppTransition.isTransitionSet()) {
+ dc.mSkipAppTransitionAnimation = true;
+ }
dc.forAllWindowContainers((wc) -> {
if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
wc.cancelAnimation();
@@ -7189,18 +7191,42 @@
private float mLatestMouseX;
private float mLatestMouseY;
- void updatePosition(float x, float y) {
+ /**
+ * The display that the pointer (mouse cursor) is currently shown on. This is updated
+ * directly by InputManagerService when the pointer display changes.
+ */
+ private int mPointerDisplayId = INVALID_DISPLAY;
+
+ /**
+ * Update the mouse cursor position as a result of a mouse movement.
+ * @return true if the position was successfully updated, false otherwise.
+ */
+ boolean updatePosition(int displayId, float x, float y) {
synchronized (this) {
mLatestEventWasMouse = true;
+
+ if (displayId != mPointerDisplayId) {
+ // The display of the position update does not match the display on which the
+ // mouse pointer is shown, so do not update the position.
+ return false;
+ }
mLatestMouseX = x;
mLatestMouseY = y;
+ return true;
+ }
+ }
+
+ void setPointerDisplayId(int displayId) {
+ synchronized (this) {
+ mPointerDisplayId = displayId;
}
}
@Override
public void onPointerEvent(MotionEvent motionEvent) {
if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
- updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+ updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
+ motionEvent.getRawY());
} else {
synchronized (this) {
mLatestEventWasMouse = false;
@@ -7210,6 +7236,7 @@
};
void updatePointerIcon(IWindow client) {
+ int pointerDisplayId;
float mouseX, mouseY;
synchronized(mMousePositionTracker) {
@@ -7218,6 +7245,7 @@
}
mouseX = mMousePositionTracker.mLatestMouseX;
mouseY = mMousePositionTracker.mLatestMouseY;
+ pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
}
synchronized (mGlobalLock) {
@@ -7234,6 +7262,10 @@
if (displayContent == null) {
return;
}
+ if (pointerDisplayId != displayContent.getDisplayId()) {
+ // Do not let the pointer icon be updated by a window on a different display.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
if (windowUnderPointer != callingWin) {
@@ -7251,7 +7283,11 @@
void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
// Mouse position tracker has not been getting updates while dragging, update it now.
- mMousePositionTracker.updatePosition(latestX, latestY);
+ if (!mMousePositionTracker.updatePosition(
+ displayContent.getDisplayId(), latestX, latestY)) {
+ // The mouse position could not be updated, so ignore this request.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(latestX, latestY);
@@ -7275,6 +7311,10 @@
}
}
+ void setMousePointerDisplayId(int displayId) {
+ mMousePositionTracker.setPointerDisplayId(displayId);
+ }
+
/**
* Update a tap exclude region in the window identified by the provided id. Touches down on this
* region will not:
@@ -7712,6 +7752,13 @@
}
@Override
+ public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ IBinder token) {
+ return mAccessibilityController
+ .getWindowTransformationMatrixAndMagnificationSpec(token);
+ }
+
+ @Override
public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
final WindowContainer container = displayId == INVALID_DISPLAY
? mRoot : mRoot.getDisplayContent(displayId);
@@ -8184,23 +8231,15 @@
}
@Override
- public void replaceInputSurfaceTouchableRegionWithWindowCrop(
- @NonNull SurfaceControl inputSurface,
- @NonNull InputWindowHandle inputWindowHandle,
- @NonNull IBinder windowToken) {
+ public boolean isPointInsideWindow(@NonNull IBinder windowToken, int displayId,
+ float displayX, float displayY) {
synchronized (mGlobalLock) {
final WindowState w = mWindowMap.get(windowToken);
- if (w == null) {
- return;
+ if (w == null || w.getDisplayId() != displayId) {
+ return false;
}
- // Make a copy of the InputWindowHandle to avoid leaking the window's
- // SurfaceControl.
- final InputWindowHandle localHandle = new InputWindowHandle(inputWindowHandle);
- localHandle.replaceTouchableRegionWithCrop(w.getSurfaceControl());
- final SurfaceControl.Transaction t = mTransactionFactory.get();
- t.setInputWindowInfo(inputSurface, localHandle);
- t.apply();
- t.close();
+
+ return w.getBounds().contains((int) displayX, (int) displayY);
}
}
}
@@ -8820,16 +8859,18 @@
WindowState newFocusTarget = displayContent == null
? null : displayContent.findFocusedWindow();
if (newFocusTarget == null) {
- ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for "
- + "win=%s dropped since no candidate was found",
+ t.setFocusedWindow(null, null, displayId).apply();
+ ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s"
+ + " dropped focus so setting focus to null since no candidate"
+ + " was found",
embeddedWindow);
return;
}
- t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
- inputToken, embeddedWindow.toString(),
+ t.setFocusedWindow(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
displayId).apply();
+
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
- "Transfer focus request " + newFocusTarget,
+ "Focus request " + newFocusTarget,
"reason=grantEmbeddedWindowFocus(false)");
}
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index d2e56fa..ed9b2f0 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -378,7 +378,7 @@
SurfaceControl.Builder makeSurface() {
final SurfaceControl.Builder builder = super.makeSurface();
if (mRoundedCornerOverlay) {
- builder.setParent(null);
+ builder.setParent(getDisplayContent().getSurfaceControl());
}
return builder;
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 93152f2..dbc1a00 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -66,13 +66,13 @@
// Defines the maximum amount of VMAs we can send per process_madvise syscall.
// Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
// iovec implementation used by process_madvise syscall
-#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
+#define MAX_VMAS_PER_BATCH UIO_MAXIOV
// Maximum bytes that we can send per process_madvise syscall once this limit
// is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
// limit is imposed by iovec implementation. However, if you want to use a smaller
-// limit, it has to be a page aligned value, otherwise, compaction would fail.
-#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
+// limit, it has to be a page aligned value.
+#define MAX_BYTES_PER_BATCH MAX_RW_COUNT
// Selected a high enough number to avoid clashing with linux errno codes
#define ERROR_COMPACTION_CANCELLED -1000
@@ -83,6 +83,180 @@
// before starting next VMA batch
static std::atomic<bool> cancelRunningCompaction;
+// A VmaBatch represents a set of VMAs that can be processed
+// as VMAs are processed by client code it is expected that the
+// VMAs get consumed which means they are discarded as they are
+// processed so that the first element always is the next element
+// to be sent
+struct VmaBatch {
+ struct iovec* vmas;
+ // total amount of VMAs to reach the end of iovec
+ size_t totalVmas;
+ // total amount of bytes that are remaining within iovec
+ uint64_t totalBytes;
+};
+
+// Advances the iterator by the specified amount of bytes.
+// This is used to remove already processed or no longer
+// needed parts of the batch.
+// Returns total bytes consumed
+uint64_t consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) {
+ if (CC_UNLIKELY(bytesToConsume) < 0) {
+ LOG(ERROR) << "Cannot consume negative bytes for VMA batch !";
+ return 0;
+ }
+
+ if (CC_UNLIKELY(bytesToConsume > batch.totalBytes)) {
+ // Avoid consuming more bytes than available
+ bytesToConsume = batch.totalBytes;
+ }
+
+ uint64_t bytesConsumed = 0;
+ while (bytesConsumed < bytesToConsume) {
+ if (CC_UNLIKELY(batch.totalVmas > 0)) {
+ // No more vmas to consume
+ break;
+ }
+ if (CC_UNLIKELY(bytesConsumed + batch.vmas[0].iov_len > bytesToConsume)) {
+ // This vma can't be fully consumed, do it partially.
+ uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed;
+ bytesConsumed += bytesLeftToConsume;
+ batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume);
+ batch.vmas[0].iov_len -= bytesLeftToConsume;
+ batch.totalBytes -= bytesLeftToConsume;
+ return bytesConsumed;
+ }
+ // This vma can be fully consumed
+ bytesConsumed += batch.vmas[0].iov_len;
+ batch.totalBytes -= batch.vmas[0].iov_len;
+ --batch.totalVmas;
+ ++batch.vmas;
+ }
+
+ return bytesConsumed;
+}
+
+// given a source of vmas this class will act as a factory
+// of VmaBatch objects and it will allow generating batches
+// until there are no more left in the source vector.
+// Note: the class does not actually modify the given
+// vmas vector, instead it iterates on it until the end.
+class VmaBatchCreator {
+ const std::vector<Vma>* sourceVmas;
+ // This is the destination array where batched VMAs will be stored
+ // it gets encapsulated into a VmaBatch which is the object
+ // meant to be used by client code.
+ struct iovec* destVmas;
+
+ // Parameters to keep track of the iterator on the source vmas
+ int currentIndex_;
+ uint64_t currentOffset_;
+
+public:
+ VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
+ : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
+
+ int currentIndex() { return currentIndex_; }
+ uint64_t currentOffset() { return currentOffset_; }
+
+ // Generates a batch and moves the iterator on the source vmas
+ // past the last VMA in the batch.
+ // Returns true on success, false on failure
+ bool createNextBatch(VmaBatch& batch) {
+ if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) {
+ return false;
+ }
+
+ const std::vector<Vma>& vmas = *sourceVmas;
+ batch.vmas = destVmas;
+ uint64_t totalBytesInBatch = 0;
+ int indexInBatch = 0;
+
+ // Add VMAs to the batch up until we consumed all the VMAs or
+ // reached any imposed limit of VMAs per batch.
+ while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
+ uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
+ uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
+ uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
+
+ batch.vmas[indexInBatch].iov_base = (void*)vmaStart;
+
+ if (vmaSize > bytesAvailableInBatch) {
+ // VMA would exceed the max available bytes in batch
+ // clamp with available bytes and finish batch.
+ vmaSize = bytesAvailableInBatch;
+ currentOffset_ += bytesAvailableInBatch;
+ }
+
+ batch.vmas[indexInBatch].iov_len = vmaSize;
+ totalBytesInBatch += vmaSize;
+
+ ++indexInBatch;
+ if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) {
+ // Reached max bytes quota so this marks
+ // the end of the batch
+ if (CC_UNLIKELY(vmaSize == (vmas[currentIndex_].end - vmaStart))) {
+ // we reached max bytes exactly at the end of the vma
+ // so advance to next one
+ currentOffset_ = 0;
+ ++currentIndex_;
+ }
+ break;
+ }
+ // Fully finished current VMA, move to next one
+ currentOffset_ = 0;
+ ++currentIndex_;
+ }
+ batch.totalVmas = indexInBatch;
+ batch.totalBytes = totalBytesInBatch;
+ if (batch.totalVmas == 0 || batch.totalBytes == 0) {
+ // This is an empty batch, mark as failed creating.
+ return false;
+ }
+ return true;
+ }
+};
+
+// Madvise a set of VMAs given in a batch for a specific process
+// The total number of bytes successfully madvised will be set on
+// outBytesProcessed.
+// Returns 0 on success and standard linux -errno code returned by
+// process_madvise on failure
+int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
+ uint64_t* outBytesProcessed) {
+ if (batch.totalVmas == 0 || batch.totalBytes == 0) {
+ // No VMAs in Batch, skip.
+ *outBytesProcessed = 0;
+ return 0;
+ }
+
+ ATRACE_BEGIN(StringPrintf("Madvise %d: %zu VMAs.", madviseType, batch.totalVmas).c_str());
+ int64_t bytesProcessedInSend =
+ process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0);
+ ATRACE_END();
+ if (CC_UNLIKELY(bytesProcessedInSend == -1)) {
+ bytesProcessedInSend = 0;
+ if (errno != EINVAL) {
+ // Forward irrecoverable errors and bail out compaction
+ *outBytesProcessed = 0;
+ return -errno;
+ }
+ }
+ if (bytesProcessedInSend == 0) {
+ // When we find a VMA with error, fully consume it as it
+ // is extremely expensive to iterate on its pages one by one
+ bytesProcessedInSend = batch.vmas[0].iov_len;
+ } else if (bytesProcessedInSend < batch.totalBytes) {
+ // Partially processed the bytes requested
+ // skip last page which is where it failed.
+ bytesProcessedInSend += PAGE_SIZE;
+ }
+ bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
+
+ *outBytesProcessed = bytesProcessedInSend;
+ return 0;
+}
+
// Legacy method for compacting processes, any new code should
// use compactProcess instead.
static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -96,8 +270,6 @@
// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
// However, if it fails for any other reason, it will bail out and forward the error
static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
- static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
-
if (vmas.empty()) {
return 0;
}
@@ -108,13 +280,16 @@
return -errno;
}
- int64_t totalBytesProcessed = 0;
+ struct iovec destVmas[MAX_VMAS_PER_BATCH];
- int64_t vmaOffset = 0;
- for (int iVma = 0; iVma < vmas.size();) {
- uint64_t bytesSentToCompact = 0;
- int iVec = 0;
- while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
+ VmaBatch batch;
+ VmaBatchCreator batcher(&vmas, destVmas);
+
+ int64_t totalBytesProcessed = 0;
+ while (batcher.createNextBatch(batch)) {
+ uint64_t bytesProcessedInSend;
+ ScopedTrace batchTrace(ATRACE_TAG, "VMA Batch");
+ do {
if (CC_UNLIKELY(cancelRunningCompaction.load())) {
// There could be a significant delay between when a compaction
// is requested and when it is handled during this time our
@@ -124,50 +299,18 @@
StringPrintf("Cancelled compaction for %d", pid).c_str());
return ERROR_COMPACTION_CANCELLED;
}
-
- uint64_t vmaStart = vmas[iVma].start + vmaOffset;
- uint64_t vmaSize = vmas[iVma].end - vmaStart;
- if (vmaSize == 0) {
- goto next_vma;
+ int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend);
+ if (error < 0) {
+ // Returns standard linux errno code
+ return error;
}
- vmasToKernel[iVec].iov_base = (void*)vmaStart;
- if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
- // Exceeded the max bytes that could be sent, so clamp
- // the end to avoid exceeding limit and issue compaction
- vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
- }
-
- vmasToKernel[iVec].iov_len = vmaSize;
- bytesSentToCompact += vmaSize;
- ++iVec;
- if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
- // Ran out of bytes within iovec, dispatch compaction.
- vmaOffset += vmaSize;
+ if (CC_UNLIKELY(bytesProcessedInSend == 0)) {
+ // This means there was a problem consuming bytes,
+ // bail out since no forward progress can be made with this batch
break;
}
-
- next_vma:
- // Finished current VMA, and have more bytes remaining
- vmaOffset = 0;
- ++iVma;
- }
-
- ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str());
- auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
- ATRACE_END();
-
- if (CC_UNLIKELY(bytesProcessed == -1)) {
- if (errno == EINVAL) {
- // This error is somewhat common due to an unevictable VMA if this is
- // the case silently skip the bad VMA and continue compacting the rest.
- continue;
- } else {
- // Forward irrecoverable errors and bail out compaction
- return -errno;
- }
- }
-
- totalBytesProcessed += bytesProcessed;
+ totalBytesProcessed += bytesProcessedInSend;
+ } while (batch.totalBytes > 0 && batch.totalVmas > 0);
}
return totalBytesProcessed;
@@ -203,6 +346,7 @@
static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
cancelRunningCompaction.store(false);
+ ATRACE_BEGIN("CollectVmas");
ProcMemInfo meminfo(pid);
std::vector<Vma> pageoutVmas, coldVmas;
auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) {
@@ -217,6 +361,7 @@
}
};
meminfo.ForEachVmaFromMaps(vmaCollectorCb);
+ ATRACE_END();
int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
if (pageoutBytes < 0) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c5ebe7..287fb82 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,7 +106,7 @@
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
- jmethodID checkInjectEventsPermission;
+ jmethodID onPointerDisplayIdChanged;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -120,7 +120,6 @@
jmethodID getLongPressTimeout;
jmethodID getPointerLayer;
jmethodID getPointerIcon;
- jmethodID getPointerDisplayId;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
@@ -277,6 +276,7 @@
void setFocusedDisplay(int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
+ void setPointerDisplayId(int32_t displayId);
void setPointerSpeed(int32_t speed);
void setPointerAcceleration(float acceleration);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
@@ -288,7 +288,6 @@
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
void setCustomPointerIcon(const SpriteIcon& icon);
void setMotionClassifierEnabled(bool enabled);
- void notifyPointerDisplayIdChanged();
/* --- InputReaderPolicyInterface implementation --- */
@@ -333,7 +332,6 @@
bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
- bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -346,6 +344,7 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId);
virtual int32_t getDefaultPointerIconId();
virtual int32_t getCustomPointerIconId();
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
private:
sp<InputManagerInterface> mInputManager;
@@ -394,7 +393,6 @@
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
- int32_t getPointerDisplayId();
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
@@ -498,13 +496,9 @@
}
}
- // Get the preferred pointer controller displayId.
- int32_t pointerDisplayId = getPointerDisplayId();
-
{ // acquire lock
AutoMutex _l(mLock);
mLocked.viewports = viewports;
- mLocked.pointerDisplayId = pointerDisplayId;
std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
if (controller != nullptr) {
controller->onDisplayViewportsUpdated(mLocked.viewports);
@@ -666,15 +660,12 @@
return controller;
}
-int32_t NativeInputManager::getPointerDisplayId() {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
+ float yPos) {
JNIEnv* env = jniEnv();
- jint pointerDisplayId = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.getPointerDisplayId);
- if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
- pointerDisplayId = ADISPLAY_ID_DEFAULT;
- }
-
- return pointerDisplayId;
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
+ xPos, yPos);
+ checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
@@ -1032,6 +1023,22 @@
: InactivityTimeout::NORMAL);
}
+void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerDisplayId == displayId) {
+ return;
+ }
+
+ ALOGI("Setting pointer display id to %d.", displayId);
+ mLocked.pointerDisplayId = displayId;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
AutoMutex _l(mLock);
@@ -1371,19 +1378,6 @@
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
}
-bool NativeInputManager::checkInjectEventsPermissionNonReentrant(int32_t injectorPid,
- int32_t injectorUid) {
- ATRACE_CALL();
- JNIEnv* env = jniEnv();
- jboolean result =
- env->CallBooleanMethod(mServiceObj, gServiceClassInfo.checkInjectEventsPermission,
- injectorPid, injectorUid);
- if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
- result = false;
- }
- return result;
-}
-
void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1494,18 +1488,6 @@
mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
}
-void NativeInputManager::notifyPointerDisplayIdChanged() {
- int32_t pointerDisplayId = getPointerDisplayId();
-
- { // acquire lock
- AutoMutex _l(mLock);
- mLocked.pointerDisplayId = pointerDisplayId;
- } // release lock
-
- mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-}
-
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -1712,10 +1694,11 @@
}
static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj,
- jint injectorPid, jint injectorUid, jint syncMode,
+ jboolean injectIntoUid, jint uid, jint syncMode,
jint timeoutMillis, jint policyFlags) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
// static_cast is safe because the value was already checked at the Java layer
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
@@ -1728,8 +1711,7 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1742,8 +1724,8 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
+ mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -2199,11 +2181,6 @@
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
- NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->notifyPointerDisplayIdChanged();
-}
-
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2321,6 +2298,11 @@
im->getInputManager()->getDispatcher().cancelCurrentTouch();
}
+static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setPointerDisplayId(displayId);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2348,7 +2330,7 @@
{"setMaximumObscuringOpacityForTouch", "(F)V",
(void*)nativeSetMaximumObscuringOpacityForTouch},
{"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode},
- {"injectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*)nativeInjectInputEvent},
+ {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent},
{"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
{"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
@@ -2393,7 +2375,6 @@
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
- {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2403,6 +2384,7 @@
{"disableSensor", "(II)V", (void*)nativeDisableSensor},
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
+ {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
};
#define FIND_CLASS(var, className) \
@@ -2495,8 +2477,8 @@
"dispatchUnhandledKey",
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
- GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
- "checkInjectEventsPermission", "(II)Z");
+ GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
+ "(IFF)V");
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
@@ -2537,9 +2519,6 @@
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "(I)Landroid/view/PointerIcon;");
- GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
- "getPointerDisplayId", "()I");
-
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
"getKeyboardLayoutOverlay",
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 3912991..0c69067 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -157,13 +157,14 @@
private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist";
private static final String TAG_SSID_DENYLIST = "ssid-denylist";
private static final String TAG_SSID = "ssid";
- private static final String ATTR_VALUE = "value";
- private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
- private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS =
"preferential_network_service_configs";
private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIG =
"preferential_network_service_config";
+ private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
+ private static final String ATTR_VALUE = "value";
+ private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
+ private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
DeviceAdminInfo info;
@@ -253,6 +254,9 @@
// List of package names to keep cached.
List<String> keepUninstalledPackages;
+ // List of packages for which the user cannot invoke "clear data" or "force stop".
+ List<String> protectedPackages;
+
// Wi-Fi SSID restriction policy.
WifiSsidPolicy mWifiSsidPolicy;
@@ -505,6 +509,7 @@
permittedNotificationListeners);
writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
+ writePackageListToXml(out, TAG_PROTECTED_PACKAGES, protectedPackages);
if (hasUserRestrictions()) {
UserRestrictionsUtils.writeRestrictions(
out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -771,6 +776,8 @@
keepUninstalledPackages = readPackageList(parser, tag);
} else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
meteredDisabledPackages = readPackageList(parser, tag);
+ } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+ protectedPackages = readPackageList(parser, tag);
} else if (TAG_USER_RESTRICTIONS.equals(tag)) {
userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
@@ -1210,6 +1217,16 @@
pw.println(keepUninstalledPackages);
}
+ if (meteredDisabledPackages != null) {
+ pw.print("meteredDisabledPackages=");
+ pw.println(meteredDisabledPackages);
+ }
+
+ if (protectedPackages != null) {
+ pw.print("protectedPackages=");
+ pw.println(protectedPackages);
+ }
+
pw.print("organizationColor=");
pw.println(organizationColor);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 48a436f..fd97db2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
@@ -129,8 +130,10 @@
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
- // List of packages protected by device owner
- List<String> mUserControlDisabledPackages = new ArrayList<>();
+ /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */
+ @Deprecated
+ @Nullable
+ List<String> mUserControlDisabledPackages;
// Bitfield of feature flags to be enabled during LockTask mode.
// We default on the power button menu, in order to be consistent with pre-P behaviour.
@@ -364,13 +367,6 @@
out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
}
- for (int i = 0, size = policyData.mUserControlDisabledPackages.size(); i < size; i++) {
- String packageName = policyData.mUserControlDisabledPackages.get(i);
- out.startTag(null, TAG_PROTECTED_PACKAGES);
- out.attribute(null, ATTR_NAME, packageName);
- out.endTag(null, TAG_PROTECTED_PACKAGES);
- }
-
if (policyData.mAppsSuspended) {
out.startTag(null, TAG_APPS_SUSPENDED);
out.attributeBoolean(null, ATTR_VALUE, policyData.mAppsSuspended);
@@ -473,7 +469,7 @@
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
- policy.mUserControlDisabledPackages.clear();
+ policy.mUserControlDisabledPackages = null;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -561,15 +557,19 @@
policy.mCurrentInputMethodSet = true;
} else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
- } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
- policy.mUserControlDisabledPackages.add(
- parser.getAttributeValue(null, ATTR_NAME));
} else if (TAG_APPS_SUSPENDED.equals(tag)) {
policy.mAppsSuspended =
parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_BYPASS_ROLE_QUALIFICATIONS.equals(tag)) {
policy.mBypassDevicePolicyManagementRoleQualifications = true;
policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE);
+ // Deprecated tags below
+ } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+ if (policy.mUserControlDisabledPackages == null) {
+ policy.mUserControlDisabledPackages = new ArrayList<>();
+ }
+ policy.mUserControlDisabledPackages.add(
+ parser.getAttributeValue(null, ATTR_NAME));
} else {
Slogf.w(TAG, "Unknown tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
@@ -674,8 +674,6 @@
pw.increaseIndent();
pw.print("mPasswordOwner="); pw.println(mPasswordOwner);
pw.print("mPasswordTokenHandle="); pw.println(Long.toHexString(mPasswordTokenHandle));
- pw.print("mUserControlDisabledPackages=");
- pw.println(mUserControlDisabledPackages);
pw.print("mAppsSuspended="); pw.println(mAppsSuspended);
pw.print("mUserSetupComplete="); pw.println(mUserSetupComplete);
pw.print("mAffiliationIds="); pw.println(mAffiliationIds);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8a7134e..1ce2417 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -536,7 +536,7 @@
// to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
// be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
// step.
- static final int DPMS_VERSION = 2;
+ static final int DPMS_VERSION = 3;
static {
SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -1908,39 +1908,12 @@
if (userHandle == UserHandle.USER_SYSTEM) {
mStateCache.setDeviceProvisioned(policy.mUserSetupComplete);
}
-
- migrateDeviceOwnerProtectedPackagesToOwners(userHandle, policy);
}
return policy;
}
}
/**
- * Only used by {@link #getUserData(int)} to migrate <b>existing</b> device owner protected
- * packages that were stored in {@link DevicePolicyData#mUserControlDisabledPackages} to
- * {@link Owners} because the device owner protected packages are now stored on a per device
- * owner basis instead of on a per user basis.
- *
- * Any calls to {@link #setUserControlDisabledPackages(ComponentName, List)} would now store
- * the device owner protected packages in {@link Owners} instead of {@link DevicePolicyData}.
- * @param userHandle The device owner user
- * @param policy The policy data of the device owner user
- */
- private void migrateDeviceOwnerProtectedPackagesToOwners(
- int userHandle, DevicePolicyData policy) {
- ComponentName deviceOwnerComponent = getOwnerComponent(userHandle);
- if (isDeviceOwner(deviceOwnerComponent, userHandle)
- && !policy.mUserControlDisabledPackages.isEmpty()) {
- mOwners.setDeviceOwnerProtectedPackages(
- deviceOwnerComponent.getPackageName(),
- policy.mUserControlDisabledPackages);
-
- policy.mUserControlDisabledPackages = new ArrayList<>();
- saveSettingsLocked(userHandle);
- }
- }
-
- /**
* Creates and loads the policy data from xml for data that is shared between
* various profiles of a user. In contrast to {@link #getUserData(int)}
* it allows access to data of users other than the calling user.
@@ -3143,6 +3116,7 @@
deleteTransferOwnershipBundleLocked(metadata.userId);
}
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
+ pushUserControlDisabledPackagesLocked(metadata.userId);
}
private void maybeLogStart() {
@@ -3182,6 +3156,7 @@
void handleStartUser(int userId) {
synchronized (getLockObject()) {
pushScreenCapturePolicy(userId);
+ pushUserControlDisabledPackagesLocked(userId);
}
pushUserRestrictions(userId);
// When system user is started (device boot), load cache for all users.
@@ -3204,6 +3179,24 @@
startOwnerService(userId, "start-user");
}
+ void pushUserControlDisabledPackagesLocked(int userId) {
+ final int targetUserId;
+ final ActiveAdmin owner;
+ if (getDeviceOwnerUserIdUncheckedLocked() == userId) {
+ owner = getDeviceOwnerAdminLocked();
+ targetUserId = UserHandle.USER_ALL;
+ } else {
+ owner = getProfileOwnerAdminLocked(userId);
+ targetUserId = userId;
+ }
+
+ List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
+ ? Collections.emptyList() : owner.protectedPackages;
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
+ targetUserId, protectedPackages));
+ }
+
@Override
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
@@ -8513,6 +8506,13 @@
}
}
+ private boolean isDeviceOwnerUserId(int userId) {
+ synchronized (getLockObject()) {
+ return mOwners.getDeviceOwnerComponent() != null
+ && mOwners.getDeviceOwnerUserId() == userId;
+ }
+ }
+
private boolean isDeviceOwnerPackage(String packageName, int userId) {
synchronized (getLockObject()) {
return mOwners.hasDeviceOwner()
@@ -8795,6 +8795,7 @@
setNetworkLoggingActiveInternal(false);
deleteTransferOwnershipBundleLocked(userId);
toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ pushUserControlDisabledPackagesLocked(userId);
}
private void clearApplicationRestrictions(int userId) {
@@ -8979,7 +8980,6 @@
policy.mLockTaskPackages.clear();
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
- policy.mUserControlDisabledPackages.clear();
saveSettingsLocked(userId);
try {
@@ -12117,10 +12117,11 @@
return;
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ Preconditions.checkCallAuthorization((isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId()))
|| isDefaultDeviceOwner(caller),
- "Caller is not profile owner or device owner;"
- + " only profile owner or device owner may control the preferential"
+ "Caller is not managed profile owner or device owner;"
+ + " only managed profile owner or device owner may control the preferential"
+ " network service");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
@@ -12147,11 +12148,12 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ Preconditions.checkCallAuthorization((isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId()))
|| isDefaultDeviceOwner(caller),
- "Caller is not profile owner or device owner;"
- + " only profile owner or device owner may retrieve the preferential"
- + " network service configurations");
+ "Caller is not managed profile owner or device owner;"
+ + " only managed profile owner or device owner may retrieve the "
+ + "preferential network service configurations");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
caller.getUserId());
@@ -16970,19 +16972,25 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(packages, "packages is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller)
+ || isFinancedDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
synchronized (getLockObject()) {
- mOwners.setDeviceOwnerProtectedPackages(who.getPackageName(), packages);
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES)
- .setAdmin(who)
- .setStrings(packages.toArray(new String[packages.size()]))
- .write();
+ ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+ if (!Objects.equals(owner.protectedPackages, packages)) {
+ owner.protectedPackages = packages.isEmpty() ? null : packages;
+ saveSettingsLocked(caller.getUserId());
+ pushUserControlDisabledPackagesLocked(caller.getUserId());
+ }
}
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES)
+ .setAdmin(who)
+ .setStrings(packages.toArray(new String[packages.size()]))
+ .write();
}
@Override
@@ -16990,11 +16998,13 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller)
+ || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
- return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
+ ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+ return deviceOwner.protectedPackages != null
+ ? deviceOwner.protectedPackages : Collections.emptyList();
}
}
@@ -18266,7 +18276,7 @@
private void updateNetworkPreferenceForUser(int userId,
List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) {
- if (!isManagedProfile(userId)) {
+ if (!isManagedProfile(userId) && !isDeviceOwnerUserId(userId)) {
return;
}
List<ProfileNetworkPreference> preferences = new ArrayList<>();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index d1c6b34..08bd3e4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -31,7 +31,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -49,7 +48,6 @@
import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -108,12 +106,6 @@
notifyChangeLocked();
pushToActivityTaskManagerLocked();
-
- for (ArrayMap.Entry<String, List<String>> entry :
- mData.mDeviceOwnerProtectedPackages.entrySet()) {
- mPackageManagerInternal.setDeviceOwnerProtectedPackages(
- entry.getKey(), entry.getValue());
- }
}
}
@@ -247,12 +239,6 @@
void clearDeviceOwner() {
synchronized (mData) {
mData.mDeviceOwnerTypes.remove(mData.mDeviceOwner.packageName);
- List<String> protectedPackages =
- mData.mDeviceOwnerProtectedPackages.remove(mData.mDeviceOwner.packageName);
- if (protectedPackages != null) {
- mPackageManagerInternal.setDeviceOwnerProtectedPackages(
- mData.mDeviceOwner.packageName, new ArrayList<>());
- }
mData.mDeviceOwner = null;
mData.mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -296,12 +282,6 @@
synchronized (mData) {
Integer previousDeviceOwnerType = mData.mDeviceOwnerTypes.remove(
mData.mDeviceOwner.packageName);
- List<String> previousProtectedPackages =
- mData.mDeviceOwnerProtectedPackages.remove(mData.mDeviceOwner.packageName);
- if (previousProtectedPackages != null) {
- mPackageManagerInternal.setDeviceOwnerProtectedPackages(
- mData.mDeviceOwner.packageName, new ArrayList<>());
- }
// We don't set a name because it's not used anyway.
// See DevicePolicyManagerService#getDeviceOwnerName
mData.mDeviceOwner = new OwnerInfo(null, target,
@@ -313,10 +293,6 @@
mData.mDeviceOwnerTypes.put(
mData.mDeviceOwner.packageName, previousDeviceOwnerType);
}
- if (previousProtectedPackages != null) {
- mData.mDeviceOwnerProtectedPackages.put(
- mData.mDeviceOwner.packageName, previousProtectedPackages);
- }
notifyChangeLocked();
pushToActivityTaskManagerLocked();
}
@@ -504,34 +480,6 @@
}
}
- void setDeviceOwnerProtectedPackages(String packageName, List<String> protectedPackages) {
- synchronized (mData) {
- if (!hasDeviceOwner()) {
- Slog.e(TAG,
- "Attempting to set device owner protected packages when there is no "
- + "device owner");
- return;
- } else if (!mData.mDeviceOwner.packageName.equals(packageName)) {
- Slog.e(TAG, "Attempting to set device owner protected packages when the provided "
- + "package name " + packageName
- + " does not match the device owner package name");
- return;
- }
-
- mData.mDeviceOwnerProtectedPackages.put(packageName, protectedPackages);
- mPackageManagerInternal.setDeviceOwnerProtectedPackages(packageName, protectedPackages);
- writeDeviceOwner();
- }
- }
-
- List<String> getDeviceOwnerProtectedPackages(String packageName) {
- synchronized (mData) {
- return mData.mDeviceOwnerProtectedPackages.containsKey(packageName)
- ? mData.mDeviceOwnerProtectedPackages.get(packageName)
- : Collections.emptyList();
- }
- }
-
void writeDeviceOwner() {
synchronized (mData) {
pushToDevicePolicyManager();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 4fe4f0d..6948420 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -91,8 +91,10 @@
// Device owner type for a managed device.
final ArrayMap<String, Integer> mDeviceOwnerTypes = new ArrayMap<>();
- final ArrayMap<String, List<String>> mDeviceOwnerProtectedPackages = new ArrayMap<>();
-
+ /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */
+ @Deprecated
+ @Nullable
+ ArrayMap<String, List<String>> mDeviceOwnerProtectedPackages;
// Internal state for the profile owner packages.
final ArrayMap<Integer, OwnerInfo> mProfileOwners = new ArrayMap<>();
@@ -366,21 +368,6 @@
}
}
- if (!mDeviceOwnerProtectedPackages.isEmpty()) {
- for (ArrayMap.Entry<String, List<String>> entry :
- mDeviceOwnerProtectedPackages.entrySet()) {
- List<String> protectedPackages = entry.getValue();
-
- out.startTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES);
- out.attribute(null, ATTR_PACKAGE, entry.getKey());
- out.attributeInt(null, ATTR_SIZE, protectedPackages.size());
- for (int i = 0, size = protectedPackages.size(); i < size; i++) {
- out.attribute(null, ATTR_NAME + i, protectedPackages.get(i));
- }
- out.endTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES);
- }
- }
-
if (mSystemUpdatePolicy != null) {
out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
mSystemUpdatePolicy.saveToXml(out);
@@ -444,6 +431,7 @@
null, ATTR_DEVICE_OWNER_TYPE_VALUE, DEVICE_OWNER_TYPE_DEFAULT);
mDeviceOwnerTypes.put(packageName, deviceOwnerType);
break;
+ // Deprecated fields below.
case TAG_DEVICE_OWNER_PROTECTED_PACKAGES:
packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
int protectedPackagesSize = parser.getAttributeInt(null, ATTR_SIZE, 0);
@@ -451,6 +439,9 @@
for (int i = 0; i < protectedPackagesSize; i++) {
protectedPackages.add(parser.getAttributeValue(null, ATTR_NAME + i));
}
+ if (mDeviceOwnerProtectedPackages == null) {
+ mDeviceOwnerProtectedPackages = new ArrayMap<>();
+ }
mDeviceOwnerProtectedPackages.put(packageName, protectedPackages);
break;
default:
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 7556d69..253851c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.UserHandle;
import android.util.Slog;
@@ -28,6 +29,8 @@
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
/**
* Class for dealing with Device Policy Manager Service version upgrades.
@@ -91,30 +94,82 @@
if (currentVersion == 1) {
Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
- // This upgrade step is for Device Owner scenario only: For devices upgrading to S,
- // if there is a device owner, it retains the ability to control sensors-related
- // permission grants.
- for (int userId : allUsers) {
- DevicePolicyData userData = allUsersData.get(userId);
- if (userData == null) {
- continue;
- }
- for (ActiveAdmin admin : userData.mAdminList) {
- if (ownersData.mDeviceOwnerUserId == userId
- && ownersData.mDeviceOwner != null
- && ownersData.mDeviceOwner.admin.equals(admin.info.getComponent())) {
- Slog.i(LOG_TAG, String.format(
- "Marking Device Owner in user %d for permission grant ", userId));
- admin.mAdminCanGrantSensorsPermissions = true;
- }
- }
- }
+ upgradeSensorPermissionsAccess(allUsers, ownersData, allUsersData);
currentVersion = 2;
}
+ if (currentVersion == 2) {
+ Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+ upgradeProtectedPackages(ownersData, allUsersData);
+ currentVersion = 3;
+ }
+
writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
}
+ /**
+ * This upgrade step is for Device Owner scenario only: For devices upgrading to S, if there is
+ * a device owner, it retains the ability to control sensors-related permission grants.
+ */
+ private void upgradeSensorPermissionsAccess(
+ int[] allUsers, OwnersData ownersData, SparseArray<DevicePolicyData> allUsersData) {
+ for (int userId : allUsers) {
+ DevicePolicyData userData = allUsersData.get(userId);
+ if (userData == null) {
+ continue;
+ }
+ for (ActiveAdmin admin : userData.mAdminList) {
+ if (ownersData.mDeviceOwnerUserId == userId
+ && ownersData.mDeviceOwner != null
+ && ownersData.mDeviceOwner.admin.equals(admin.info.getComponent())) {
+ Slog.i(LOG_TAG, String.format(
+ "Marking Device Owner in user %d for permission grant ", userId));
+ admin.mAdminCanGrantSensorsPermissions = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * This upgrade step moves device owner protected packages to ActiveAdmin.
+ * Initially these packages were stored in DevicePolicyData, then moved to Owners without
+ * employing PolicyVersionUpgrader. Here we check both places.
+ */
+ private void upgradeProtectedPackages(
+ OwnersData ownersData, SparseArray<DevicePolicyData> allUsersData) {
+ if (ownersData.mDeviceOwner == null) {
+ return;
+ }
+ List<String> protectedPackages = null;
+ DevicePolicyData doUserData = allUsersData.get(ownersData.mDeviceOwnerUserId);
+ if (doUserData == null) {
+ Slog.e(LOG_TAG, "No policy data for do user");
+ return;
+ }
+ if (ownersData.mDeviceOwnerProtectedPackages != null) {
+ protectedPackages = ownersData.mDeviceOwnerProtectedPackages
+ .get(ownersData.mDeviceOwner.packageName);
+ if (protectedPackages != null) {
+ Slog.i(LOG_TAG, "Found protected packages in Owners");
+ }
+ ownersData.mDeviceOwnerProtectedPackages = null;
+ } else if (doUserData.mUserControlDisabledPackages != null) {
+ Slog.i(LOG_TAG, "Found protected packages in DevicePolicyData");
+ protectedPackages = doUserData.mUserControlDisabledPackages;
+ doUserData.mUserControlDisabledPackages = null;
+ }
+
+ ActiveAdmin doAdmin = doUserData.mAdminMap.get(ownersData.mDeviceOwner.admin);
+ if (doAdmin == null) {
+ Slog.e(LOG_TAG, "DO admin not found in DO user");
+ return;
+ }
+
+ if (protectedPackages != null) {
+ doAdmin.protectedPackages = new ArrayList<>(protectedPackages);
+ }
+ }
+
private OwnersData loadOwners(int[] allUsers) {
OwnersData ownersData = new OwnersData(mPathProvider);
ownersData.load(allUsers);
@@ -146,17 +201,23 @@
OwnersData ownersData) {
final SparseArray<DevicePolicyData> allUsersData = new SparseArray<>();
for (int user: allUsers) {
- ComponentName owner = null;
- if (ownersData.mDeviceOwnerUserId == user && ownersData.mDeviceOwner != null) {
- owner = ownersData.mDeviceOwner.admin;
- } else if (ownersData.mProfileOwners.containsKey(user)) {
- owner = ownersData.mProfileOwners.get(user).admin;
- }
+ ComponentName owner = getOwnerForUser(ownersData, user);
allUsersData.append(user, loadDataForUser(user, loadVersion, owner));
}
return allUsersData;
}
+ @Nullable
+ private ComponentName getOwnerForUser(OwnersData ownersData, int user) {
+ ComponentName owner = null;
+ if (ownersData.mDeviceOwnerUserId == user && ownersData.mDeviceOwner != null) {
+ owner = ownersData.mDeviceOwner.admin;
+ } else if (ownersData.mProfileOwners.containsKey(user)) {
+ owner = ownersData.mProfileOwners.get(user).admin;
+ }
+ return owner;
+ }
+
private DevicePolicyData loadDataForUser(
int userId, int loadVersion, ComponentName ownerComponent) {
DevicePolicyData policy = new DevicePolicyData(userId);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dc2b6f8..80f0186 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,8 +338,6 @@
"com.android.server.contentcapture.ContentCaptureManagerService";
private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
"com.android.server.translation.TranslationManagerService";
- private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
- "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -2635,11 +2633,6 @@
Slog.d(TAG, "TranslationService not defined by OEM");
}
- // Selection toolbar service
- t.traceBegin("StartSelectionToolbarManagerService");
- mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
- t.traceEnd();
-
// NOTE: ClipboardService depends on ContentCapture and Autofill
t.traceBegin("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
diff --git a/services/proguard.flags b/services/proguard.flags
index 425da6c..bad02b4 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -33,6 +33,11 @@
public <init>(...);
}
+# Accessed from com.android.compos APEX
+-keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog {
+ public static void write(...);
+}
+
# Binder interfaces
-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
deleted file mode 100644
index cc6405f..0000000
--- a/services/selectiontoolbar/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
- name: "services.selectiontoolbar-sources",
- srcs: ["java/**/*.java"],
- path: "java",
- visibility: ["//frameworks/base/services"],
-}
-
-java_library_static {
- name: "services.selectiontoolbar",
- defaults: ["platform_service_defaults"],
- srcs: [":services.selectiontoolbar-sources"],
- libs: ["services.core"],
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 8461b39..17b4226 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3220,8 +3220,7 @@
when(mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)).thenReturn(
Arrays.asList(package4));
- mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
- mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = false;
+ mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
package1,
package3,
@@ -3233,21 +3232,7 @@
assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3));
assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
- mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
- mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true;
- mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
- package1,
- package3,
- });
-
- // Same as above, deny listed packages will be false.
- assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1));
- assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2));
- assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3));
- assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
-
mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
- mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true;
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
package1,
package3,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index ac54293..009dae5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -582,6 +582,7 @@
DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+ DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null;
DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null;
@@ -617,6 +618,14 @@
R.integer.config_bg_current_drain_window));
bgCurrentDrainWindow.set(windowMs);
+ bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+ DeviceConfig::getLong,
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_window));
+ bgCurrentDrainInteractionGracePeriod.set(windowMs);
+
bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
@@ -768,6 +777,32 @@
clearInvocations(mInjector.getAppStandbyInternal());
+ // It won't be restricted since user just interacted with it.
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ zeros, new double[]{0, restrictBucketThresholdMah - 1},
+ zeros, new double[]{restrictBucketThresholdMah + 1, 0},
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ doReturn(mCurrentTimeMillis + windowMs)
+ .when(stats).getStatsEndTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ anyInt(), anyInt());
+ });
+
+ // Sleep a while.
+ Thread.sleep(windowMs);
+ clearInvocations(mInjector.getAppStandbyInternal());
+ // Now it should have been restricted.
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
zeros, new double[]{0, restrictBucketThresholdMah - 1},
zeros, new double[]{restrictBucketThresholdMah + 1, 0},
@@ -1061,6 +1096,7 @@
} finally {
closeIfNotNull(bgCurrentDrainMonitor);
closeIfNotNull(bgCurrentDrainWindow);
+ closeIfNotNull(bgCurrentDrainInteractionGracePeriod);
closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
closeIfNotNull(bgPromptFgsWithNotiToBgRestricted);
@@ -1610,6 +1646,7 @@
DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+ DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null;
DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketHighThreshold = null;
@@ -1655,6 +1692,14 @@
R.integer.config_bg_current_drain_window));
bgCurrentDrainWindow.set(windowMs);
+ bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+ DeviceConfig::getLong,
+ (long) mContext.getResources().getInteger(
+ R.integer.config_bg_current_drain_window));
+ bgCurrentDrainInteractionGracePeriod.set(windowMs);
+
bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
@@ -2176,6 +2221,7 @@
} finally {
closeIfNotNull(bgCurrentDrainMonitor);
closeIfNotNull(bgCurrentDrainWindow);
+ closeIfNotNull(bgCurrentDrainInteractionGracePeriod);
closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 444db91..da5c8f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -16,6 +16,9 @@
package com.android.server.pm;
+import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED;
+import static com.android.server.pm.BackgroundDexOptService.STATUS_OK;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -23,12 +26,14 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -38,10 +43,13 @@
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.PowerManager;
+import android.util.Log;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PinnerService;
import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.DexoptOptions;
import org.junit.After;
import org.junit.Before;
@@ -52,7 +60,11 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
@@ -66,8 +78,12 @@
private static final long TEST_WAIT_TIMEOUT_MS = 10_000;
- private static final List<String> DEFAULT_PACKAGE_LIST = List.of("aaa", "bbb");
- private static final List<String> EMPTY_PACKAGE_LIST = List.of();
+ private static final String PACKAGE_AAA = "aaa";
+ private static final List<String> DEFAULT_PACKAGE_LIST = List.of(PACKAGE_AAA, "bbb");
+ private int mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
+
+ // Store expected dexopt sequence for verification.
+ private ArrayList<DexOptInfo> mDexInfoSequence = new ArrayList<>();
@Mock
private Context mContext;
@@ -116,14 +132,23 @@
when(mInjector.getDexOptThermalCutoff()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL);
when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_NONE);
when(mInjector.supportSecondaryDex()).thenReturn(true);
- when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST);
- when(mDexOptHelper.performDexOptWithStatus(any())).thenReturn(
- PackageDexOptimizer.DEX_OPT_PERFORMED);
- when(mDexOptHelper.performDexOpt(any())).thenReturn(true);
+ setupDexOptHelper();
mService = new BackgroundDexOptService(mInjector);
}
+ private void setupDexOptHelper() {
+ when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST);
+ when(mDexOptHelper.performDexOptWithStatus(any())).thenAnswer(inv -> {
+ DexoptOptions opt = inv.getArgument(0);
+ if (opt.getPackageName().equals(PACKAGE_AAA)) {
+ return mDexOptResultForPackageAAA;
+ }
+ return PackageDexOptimizer.DEX_OPT_PERFORMED;
+ });
+ when(mDexOptHelper.performDexOpt(any())).thenReturn(true);
+ }
+
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(BackgroundDexOptService.class);
@@ -159,7 +184,7 @@
@Test
public void testNoExecutionForNoOptimizablePackages() {
initUntilBootCompleted();
- when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(EMPTY_PACKAGE_LIST);
+ when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList());
assertThat(mService.onStartJob(mJobServiceForPostBoot,
mJobParametersForPostBoot)).isFalse();
@@ -170,15 +195,70 @@
public void testPostBootUpdateFullRun() {
initUntilBootCompleted();
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, false, 1);
+ runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+ }
+
+ @Test
+ public void testPostBootUpdateFullRunWithPackageFailure() {
+ mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
+
+ initUntilBootCompleted();
+
+ runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
+
+ assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
+ assertThat(getFailedPackageNamesSecondary()).isEmpty();
}
@Test
public void testIdleJobFullRun() {
initUntilBootCompleted();
+ runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+ runFullJob(mJobServiceForIdle, mJobParametersForIdle,
+ /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+ }
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, false, 1);
- runFullJob(mJobServiceForIdle, mJobParametersForIdle, true, 2);
+ @Test
+ public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() {
+ mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
+
+ initUntilBootCompleted();
+
+ runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
+
+ assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
+ assertThat(getFailedPackageNamesSecondary()).isEmpty();
+
+ runFullJob(mJobServiceForIdle, mJobParametersForIdle,
+ /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
+
+ assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
+ assertThat(getFailedPackageNamesSecondary()).isEmpty();
+
+ mService.notifyPackageChanged(PACKAGE_AAA);
+
+ assertThat(getFailedPackageNamesPrimary()).isEmpty();
+ assertThat(getFailedPackageNamesSecondary()).isEmpty();
+
+ // Succeed this time.
+ mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
+
+ runFullJob(mJobServiceForIdle, mJobParametersForIdle,
+ /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null);
+
+ assertThat(getFailedPackageNamesPrimary()).isEmpty();
+ assertThat(getFailedPackageNamesSecondary()).isEmpty();
}
@Test
@@ -404,8 +484,10 @@
}
private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params,
- boolean expectedReschedule, int totalJobRuns) {
+ boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams,
+ @Nullable String expectedSkippedPackage) {
when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
+ addFullRunSequence(expectedSkippedPackage);
assertThat(mService.onStartJob(jobService, params)).isTrue();
ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
@@ -413,20 +495,99 @@
argThreadRunnable.getValue().run();
- verify(jobService).jobFinished(params, expectedReschedule);
+ verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params,
+ expectedReschedule);
// Never block
verify(mDexOptHelper, never()).controlDexOptBlocking(true);
- verifyPerformDexOpt(DEFAULT_PACKAGE_LIST, totalJobRuns);
+ verifyPerformDexOpt();
+ assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus);
}
- private void verifyPerformDexOpt(List<String> pkgs, int expectedRuns) {
+ private void verifyPerformDexOpt() {
InOrder inOrder = inOrder(mDexOptHelper);
- for (int i = 0; i < expectedRuns; i++) {
- for (String pkg : pkgs) {
- inOrder.verify(mDexOptHelper, times(1)).performDexOptWithStatus(argThat((option) ->
- option.getPackageName().equals(pkg) && !option.isDexoptOnlySecondaryDex()));
- inOrder.verify(mDexOptHelper, times(1)).performDexOpt(argThat((option) ->
- option.getPackageName().equals(pkg) && option.isDexoptOnlySecondaryDex()));
+ inOrder.verify(mDexOptHelper).getOptimizablePackages(any());
+ for (DexOptInfo info : mDexInfoSequence) {
+ if (info.isPrimary) {
+ verify(mDexOptHelper).performDexOptWithStatus(
+ argThat((option) -> option.getPackageName().equals(info.packageName)
+ && !option.isDexoptOnlySecondaryDex()));
+ } else {
+ inOrder.verify(mDexOptHelper).performDexOpt(
+ argThat((option) -> option.getPackageName().equals(info.packageName)
+ && option.isDexoptOnlySecondaryDex()));
+ }
+ }
+
+ // Even InOrder cannot check the order if the same call is made multiple times.
+ // To check the order across multiple runs, we reset the mock so that order can be checked
+ // in each call.
+ mDexInfoSequence.clear();
+ reset(mDexOptHelper);
+ setupDexOptHelper();
+ }
+
+ private String findDumpValueForKey(String key) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(out, true);
+ IndentingPrintWriter writer = new IndentingPrintWriter(pw, "");
+ try {
+ mService.dump(writer);
+ writer.flush();
+ Log.i(TAG, "dump output:" + out.toString());
+ for (String line : out.toString().split(System.lineSeparator())) {
+ String[] vals = line.split(":");
+ if (vals[0].equals(key)) {
+ if (vals.length == 2) {
+ return vals[1].strip();
+ } else {
+ break;
+ }
+ }
+ }
+ return "";
+ } finally {
+ writer.close();
+ }
+ }
+
+ List<String> findStringListFromDump(String key) {
+ String values = findDumpValueForKey(key);
+ if (values.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(values.split(","));
+ }
+
+ private List<String> getFailedPackageNamesPrimary() {
+ return findStringListFromDump("mFailedPackageNamesPrimary");
+ }
+
+ private List<String> getFailedPackageNamesSecondary() {
+ return findStringListFromDump("mFailedPackageNamesSecondary");
+ }
+
+ private int getLastExecutionStatus() {
+ return Integer.parseInt(findDumpValueForKey("mLastExecutionStatus"));
+ }
+
+ private static class DexOptInfo {
+ public final String packageName;
+ public final boolean isPrimary;
+
+ private DexOptInfo(String packageName, boolean isPrimary) {
+ this.packageName = packageName;
+ this.isPrimary = isPrimary;
+ }
+ }
+
+ private void addFullRunSequence(@Nullable String expectedSkippedPackage) {
+ for (String packageName : DEFAULT_PACKAGE_LIST) {
+ if (packageName.equals(expectedSkippedPackage)) {
+ // only fails primary dexopt in mocking but add secodary
+ mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
+ } else {
+ mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ true));
+ mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index fa3e05a..20cfd59 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -24,20 +24,33 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
+import android.os.Handler;
+import android.os.Looper;
import android.permission.PermissionManager;
import android.util.ArraySet;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -53,26 +66,43 @@
public class CameraPrivacyLightControllerTest {
+ private int mDayColor = 1;
+ private int mNightColor = 0;
+ private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000;
+ private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15);
+
private MockitoSession mMockitoSession;
@Mock
private Context mContext;
@Mock
+ private Resources mResources;
+
+ @Mock
private LightsManager mLightsManager;
@Mock
private AppOpsManager mAppOpsManager;
@Mock
+ private SensorManager mSensorManager;
+
+ @Mock
private LightsManager.LightsSession mLightsSession;
+ @Mock
+ private Sensor mLightSensor;
+
private ArgumentCaptor<AppOpsManager.OnOpActiveChangedListener> mAppOpsListenerCaptor =
ArgumentCaptor.forClass(AppOpsManager.OnOpActiveChangedListener.class);
private ArgumentCaptor<LightsRequest> mLightsRequestCaptor =
ArgumentCaptor.forClass(LightsRequest.class);
+ private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+
private Set<String> mExemptedPackages = new ArraySet<>();
private List<Light> mLights = new ArrayList<>();
@@ -86,11 +116,22 @@
.spyStatic(PermissionManager.class)
.startMocking();
+ doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day);
+ doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night);
+
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources)
+ .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
+ doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources)
+ .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold);
+
doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class);
doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
doReturn(mLights).when(mLightsManager).getLights();
doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
+ doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
doReturn(mExemptedPackages)
.when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
@@ -107,7 +148,7 @@
@Test
public void testAppsOpsListenerNotRegisteredWithoutCameraLights() {
mLights.add(getNextLight(false));
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any());
}
@@ -116,7 +157,7 @@
public void testAppsOpsListenerRegisteredWithCameraLight() {
mLights.add(getNextLight(true));
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any());
}
@@ -128,14 +169,13 @@
mLights.add(getNextLight(r.nextBoolean()));
}
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
// Verify no session has been opened at this point.
verify(mLightsManager, times(0)).openSession(anyInt());
// Set camera op as active.
- verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
- mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
+ openCamera();
// Verify session has been opened exactly once
verify(mLightsManager, times(1)).openSession(anyInt());
@@ -161,7 +201,7 @@
public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() {
mLights.add(getNextLight(true));
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
@@ -176,7 +216,7 @@
public void testWillCloseOnFinishOp() {
mLights.add(getNextLight(true));
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
@@ -192,7 +232,7 @@
public void testWillCloseOnFinishOpForAllPackages() {
mLights.add(getNextLight(true));
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
int numUids = 100;
List<Integer> uids = new ArrayList<>(numUids);
@@ -226,7 +266,7 @@
mLights.add(getNextLight(true));
mExemptedPackages.add("pkg1");
- new CameraPrivacyLightController(mContext);
+ createCameraPrivacyLightController();
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
@@ -235,6 +275,147 @@
verify(mLightsManager, times(0)).openSession(anyInt());
}
+ @Test
+ public void testNoLightSensor() {
+ mLights.add(getNextLight(true));
+ doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+
+ createCameraPrivacyLightController();
+
+ openCamera();
+
+ verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
+ LightsRequest lightsRequest = mLightsRequestCaptor.getValue();
+ for (LightState lightState : lightsRequest.getLightStates()) {
+ assertEquals(mDayColor, lightState.getColor());
+ }
+ }
+
+ @Test
+ public void testALSListenerNotRegisteredUntilCameraIsOpened() {
+ mLights.add(getNextLight(true));
+ Sensor sensor = mock(Sensor.class);
+ doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+
+ CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+
+ verify(mSensorManager, never()).registerListener(any(SensorEventListener.class),
+ any(Sensor.class), anyInt(), any(Handler.class));
+
+ openCamera();
+
+ verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(),
+ any(Sensor.class), anyInt(), any(Handler.class));
+
+ mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false);
+ verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue());
+ }
+
+ @Ignore
+ @Test
+ public void testDayColor() {
+ testBrightnessToColor(20, mDayColor);
+ }
+
+ @Ignore
+ @Test
+ public void testNightColor() {
+ testBrightnessToColor(10, mNightColor);
+ }
+
+ private void testBrightnessToColor(int brightnessValue, int color) {
+ mLights.add(getNextLight(true));
+ Sensor sensor = mock(Sensor.class);
+ doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+
+ CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+ cplc.setElapsedRealTime(0);
+
+ openCamera();
+
+ verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
+ any(Sensor.class), anyInt(), any(Handler.class));
+ SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
+ float[] sensorEventValues = new float[1];
+ SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues);
+
+ sensorEventValues[0] = getLightSensorValue(brightnessValue);
+ sensorListener.onSensorChanged(sensorEvent);
+
+ verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
+ for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+ assertEquals(color, lightState.getColor());
+ }
+ }
+
+ @Ignore
+ @Test
+ public void testDayToNightTransistion() {
+ mLights.add(getNextLight(true));
+ Sensor sensor = mock(Sensor.class);
+ doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+
+ CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+ cplc.setElapsedRealTime(0);
+
+ openCamera();
+ // There will be an initial call at brightness 0
+ verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class));
+
+ verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
+ any(Sensor.class), anyInt(), any(Handler.class));
+ SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
+
+ onSensorEvent(cplc, sensorListener, sensor, 0, 20);
+
+ // 5 sec avg = 20
+ onSensorEvent(cplc, sensorListener, sensor, 5000, 30);
+
+ verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
+ for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+ assertEquals(mDayColor, lightState.getColor());
+ }
+
+ // 5 sec avg = 22
+
+ onSensorEvent(cplc, sensorListener, sensor, 6000, 10);
+
+ // 5 sec avg = 18
+
+ onSensorEvent(cplc, sensorListener, sensor, 8000, 5);
+
+ // Should have always been day
+ verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
+ for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+ assertEquals(mDayColor, lightState.getColor());
+ }
+
+ // 5 sec avg = 12
+
+ onSensorEvent(cplc, sensorListener, sensor, 10000, 5);
+
+ // Should now be night
+ verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture());
+ for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+ assertEquals(mNightColor, lightState.getColor());
+ }
+ }
+
+ private void onSensorEvent(CameraPrivacyLightController cplc,
+ SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) {
+ cplc.setElapsedRealTime(timestamp);
+ sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp,
+ new float[] {getLightSensorValue(value)}));
+ }
+
+ // Use the test thread so that the test is deterministic
+ private CameraPrivacyLightController createCameraPrivacyLightController() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ return new CameraPrivacyLightController(mContext, Looper.myLooper());
+ }
+
private Light getNextLight(boolean cameraType) {
Light light = ExtendedMockito.mock(Light.class);
if (cameraType) {
@@ -245,4 +426,13 @@
doReturn(mNextLightId++).when(light).getId();
return light;
}
+
+ private float getLightSensorValue(int i) {
+ return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER);
+ }
+
+ private void openCamera() {
+ verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
+ mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true);
+ }
}
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml
new file mode 100644
index 0000000..0725d25
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<root>
+ <device-owner package="com.android.frameworks.servicestests" name="" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" userRestrictionsMigrated="true" isPoOrganizationOwnedDevice="true" />
+ <device-owner-context userId="0" />
+ <device-owner-protected-packages package="com.android.frameworks.servicestests" size="2" name0="com.some.app" name1="foo.bar.baz" />
+</root>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml
new file mode 100644
index 0000000..2d06ee6
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991" />
+ <strong-auth-unlock-timeout value="0" />
+ <test-only-admin value="true" />
+ <cross-profile-calendar-packages />
+ <cross-profile-packages />
+ </admin>
+ <lock-task-features value="16" />
+ <protected-packages name="com.some.app" />
+ <protected-packages name="foo.bar.baz" />
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 08df438..19df5a2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -89,8 +89,10 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.util.Pair;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -145,6 +147,8 @@
private static final int USER_ID = 1;
private static final int USER_ID2 = 2;
private static final int INTERACTION_ID = 199;
+ private static final Pair<float[], MagnificationSpec> FAKE_MATRIX_AND_MAG_SPEC =
+ new Pair<>(new float[9], new MagnificationSpec());
private static final int PID = Process.myPid();
private static final long TID = Process.myTid();
private static final int UID = Process.myUid();
@@ -188,6 +192,8 @@
.thenReturn(mMockFingerprintGestureDispatcher);
when(mMockSystemSupport.getMagnificationProcessor())
.thenReturn(mMockMagnificationProcessor);
+ when(mMockSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(anyInt()))
+ .thenReturn(FAKE_MATRIX_AND_MAG_SPEC);
PowerManager powerManager =
new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index 18e0f29..bce99a0 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -416,6 +416,7 @@
null /* resolvedType */,
null /* requiredPermissions */,
null /* excludedPermissions */,
+ null /* excludedPackages */,
0 /* appOp */,
null /* options */,
new ArrayList<>(receivers), // Make a copy to not affect the original list.
diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
index d1390c6..e68a8a0 100644
--- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
@@ -49,10 +49,11 @@
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
// Different processes and tags should not get rate limited either.
assertFalse(mRateLimiter.shouldRateLimit("tag", "process2").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag2", "process").shouldRateLimit());
- // The 6th entry of the same process should be rate limited.
+ // The 7th entry of the same process should be rate limited.
assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
}
@@ -64,12 +65,13 @@
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
- // The 6th entry of the same process should be rate limited.
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+ // The 7th entry of the same process should be rate limited.
assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
- // After 11 seconds there should be nothing left in the buffer and the same type of entry
+ // After 11 minutes there should be nothing left in the buffer and the same type of entry
// should not get rate limited anymore.
- mClock.setOffsetMillis(11000);
+ mClock.setOffsetMillis(11 * 60 * 1000);
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
}
@@ -86,13 +88,15 @@
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(0,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
+ assertEquals(0,
+ mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(1,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(2,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
- // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop.
- mClock.setOffsetMillis(11000);
+ // After 11 minutes the rate limiting buffer will be cleared and rate limiting will stop.
+ mClock.setOffsetMillis(11 * 60 * 1000);
// The first call after rate limiting stops will still return the number of dropped events.
assertEquals(2,
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 5b3a128..98f0603 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -339,7 +339,7 @@
ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mIActivityManager, times(2)).broadcastIntentWithFeature(any(), any(),
intentArgumentCaptor.capture(), any(), any(), anyInt(), any(), any(), any(), any(),
- anyInt(), any(), anyBoolean(), anyBoolean(), eq(USER_ID_1));
+ any(), anyInt(), any(), anyBoolean(), anyBoolean(), eq(USER_ID_1));
List<Intent> capturedIntents = intentArgumentCaptor.getAllValues();
assertEquals(capturedIntents.get(0).getAction(), Intent.ACTION_LOCKED_BOOT_COMPLETED);
assertEquals(capturedIntents.get(1).getAction(), Intent.ACTION_BOOT_COMPLETED);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index b4bb04d..77cbb3a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,22 +19,22 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.server.LocalServices;
import org.junit.Before;
@@ -44,7 +44,8 @@
import org.mockito.MockitoAnnotations;
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class InputControllerTest {
@Mock
@@ -56,13 +57,16 @@
@Mock
private IInputManager mIInputManagerMock;
+ private InputManagerMockHelper mInputManagerMockHelper;
private InputController mInputController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -72,10 +76,10 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- InputManager.resetInstance(mIInputManagerMock);
- doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
- doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
}
@Test
@@ -83,6 +87,7 @@
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mInputController.unregisterInputDevice(deviceToken);
@@ -95,10 +100,12 @@
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
final IBinder deviceToken2 = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
+ verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
new file mode 100644
index 0000000..aa2d97e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.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.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * A test utility class used to share the logic for setting up {@link InputManager}'s callback for
+ * when a virtual input device being added.
+ */
+class InputManagerMockHelper {
+ private final TestableLooper mTestableLooper;
+ private final InputController.NativeWrapper mNativeWrapperMock;
+ private final IInputManager mIInputManagerMock;
+ private final List<InputDevice> mDevices = new ArrayList<>();
+ private IInputDevicesChangedListener mDevicesChangedListener;
+
+ InputManagerMockHelper(TestableLooper testableLooper,
+ InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
+ throws Exception {
+ mTestableLooper = testableLooper;
+ mNativeWrapperMock = nativeWrapperMock;
+ mIInputManagerMock = iInputManagerMock;
+
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
+ anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
+
+ doAnswer(inv -> {
+ mDevicesChangedListener = inv.getArgument(0);
+ return null;
+ }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+ doAnswer(inv -> mDevices.get(inv.getArgument(0)))
+ .when(mIInputManagerMock).getInputDevice(anyInt());
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+
+ // Set a new instance of InputManager for testing that uses the IInputManager mock as the
+ // interface to the server.
+ InputManager.resetInstance(mIInputManagerMock);
+ }
+
+ private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
+ Objects.requireNonNull(mDevicesChangedListener,
+ "InputController did not register an InputDevicesChangedListener.");
+ // We only use a subset of the fields of InputDevice in InputController.
+ final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
+ inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
+ inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
+ true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
+ null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
+ false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
+ mDevices.add(device);
+ try {
+ mDevicesChangedListener.onInputDevicesChanged(
+ mDevices.stream().flatMapToInt(
+ d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
+ } catch (RemoteException ignored) {
+ }
+ // Process the device added notification.
+ mTestableLooper.processAllMessages();
+ return null;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 808f8c2c..cbb9fd7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -54,6 +54,7 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -118,6 +119,7 @@
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
+ private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private AssociationInfo mAssociationInfo;
@@ -146,6 +148,8 @@
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
private ApplicationInfo mApplicationInfoMock;
+ @Mock
+ IInputManager mIInputManagerMock;
private ArraySet<ComponentName> getBlockedActivities() {
ArraySet<ComponentName> blockedActivities = new ArraySet<>();
@@ -170,13 +174,13 @@
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -199,7 +203,13 @@
new Handler(TestableLooper.get(this).getLooper()));
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
+
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0fc201e..ec6b674 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -6996,22 +6996,23 @@
dpm.setUserControlDisabledPackages(admin1, testPackages);
verify(getServices().packageManagerInternal)
- .setDeviceOwnerProtectedPackages(admin1.getPackageName(), testPackages);
+ .setOwnerProtectedPackages(UserHandle.USER_ALL, testPackages);
assertThat(dpm.getUserControlDisabledPackages(admin1)).isEqualTo(testPackages);
}
@Test
- public void testSetUserControlDisabledPackages_failingAsPO() {
+ public void testSetUserControlDisabledPackages_asPO() {
final List<String> testPackages = new ArrayList<>();
testPackages.add("package_1");
testPackages.add("package_2");
mServiceContext.permissions.add(permission.MANAGE_DEVICE_ADMINS);
setAsProfileOwner(admin1);
- assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpm.setUserControlDisabledPackages(admin1, testPackages));
- assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpm.getUserControlDisabledPackages(admin1));
+ dpm.setUserControlDisabledPackages(admin1, testPackages);
+
+ verify(getServices().packageManagerInternal)
+ .setOwnerProtectedPackages(CALLER_USER_HANDLE, testPackages);
+ assertThat(dpm.getUserControlDisabledPackages(admin1)).isEqualTo(testPackages);
}
private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
@@ -7845,7 +7846,7 @@
dpm.setUserControlDisabledPackages(admin1, packages);
verify(getServices().packageManagerInternal)
- .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+ .setOwnerProtectedPackages(eq(UserHandle.USER_ALL), eq(packages));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index 9efc10c..412722d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -63,8 +63,9 @@
public class PolicyVersionUpgraderTest extends DpmTestBase {
// NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
// to the new version.
- private static final int LATEST_TESTED_VERSION = 2;
+ private static final int LATEST_TESTED_VERSION = 3;
public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
+ public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
private ComponentName mFakeAdmin;
private class FakePolicyUpgraderDataProvider implements PolicyUpgraderDataProvider {
@@ -119,18 +120,18 @@
mUpgrader = new PolicyVersionUpgrader(mProvider, getServices().pathProvider);
mFakeAdmin = new ComponentName(
"com.android.frameworks.servicestests",
- "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1");
+ "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1");
ActivityInfo activityInfo = createActivityInfo(mFakeAdmin);
DeviceAdminInfo dai = createDeviceAdminInfo(activityInfo);
mProvider.mComponentToDeviceAdminInfo.put(mFakeAdmin, dai);
- mProvider.mUsers = new int[] {0};
+ mProvider.mUsers = new int[]{0};
}
@Test
public void testSameVersionDoesNothing() throws IOException {
writeVersionToXml(DevicePolicyManagerService.DPMS_VERSION);
final int userId = mProvider.mUsers[0];
- preparePoliciesFile(userId);
+ preparePoliciesFile(userId, "device_policies.xml");
String oldContents = readPoliciesFile(userId);
mUpgrader.upgradePolicy(DevicePolicyManagerService.DPMS_VERSION);
@@ -142,19 +143,19 @@
@Test
public void testUpgrade0To1RemovesPasswordMetrics() throws IOException, XmlPullParserException {
final String activePasswordTag = "active-password";
- mProvider.mUsers = new int[] {0, 10};
+ mProvider.mUsers = new int[]{0, 10};
getServices().addUser(10, /* flags= */ 0, USER_TYPE_PROFILE_MANAGED);
writeVersionToXml(0);
for (int userId : mProvider.mUsers) {
- preparePoliciesFile(userId);
+ preparePoliciesFile(userId, "device_policies.xml");
}
// Validate test set-up.
assertThat(isTagPresent(readPoliciesFileToStream(0), activePasswordTag)).isTrue();
mUpgrader.upgradePolicy(1);
- assertThat(readVersionFromXml()).isGreaterThan(1);
- for (int user: mProvider.mUsers) {
+ assertThat(readVersionFromXml()).isAtLeast(1);
+ for (int user : mProvider.mUsers) {
assertThat(isTagPresent(readPoliciesFileToStream(user), activePasswordTag)).isFalse();
}
}
@@ -163,17 +164,17 @@
public void testUpgrade1To2MarksDoForPermissionControl()
throws IOException, XmlPullParserException {
final int ownerUser = 10;
- mProvider.mUsers = new int[] {0, ownerUser};
+ mProvider.mUsers = new int[]{0, ownerUser};
getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM);
writeVersionToXml(1);
for (int userId : mProvider.mUsers) {
- preparePoliciesFile(userId);
+ preparePoliciesFile(userId, "device_policies.xml");
}
- prepareDeviceOwnerFile(ownerUser);
+ prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml");
mUpgrader.upgradePolicy(2);
- assertThat(readVersionFromXml()).isEqualTo(2);
+ assertThat(readVersionFromXml()).isAtLeast(2);
assertThat(getBooleanValueTag(readPoliciesFileToStream(mProvider.mUsers[0]),
PERMISSIONS_TAG)).isFalse();
assertThat(getBooleanValueTag(readPoliciesFileToStream(ownerUser),
@@ -186,8 +187,8 @@
getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM);
setUpPackageManagerForAdmin(admin1, UserHandle.getUid(ownerUser, 123 /* admin app ID */));
writeVersionToXml(0);
- preparePoliciesFile(ownerUser);
- prepareDeviceOwnerFile(ownerUser);
+ preparePoliciesFile(ownerUser, "device_policies.xml");
+ prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml");
DevicePolicyManagerServiceTestable dpms;
final long ident = getContext().binder.clearCallingIdentity();
@@ -202,11 +203,65 @@
getContext().binder.restoreCallingIdentity(ident);
}
+ assertThat(readVersionFromXml()).isEqualTo(DevicePolicyManagerService.DPMS_VERSION);
+
// DO should be marked as able to grant sensors permission during upgrade and should be
// reported as such via the API.
assertThat(dpms.canAdminGrantSensorsPermissionsForUser(ownerUser)).isTrue();
}
+ /**
+ * Up to Android R DO protected packages were stored in DevicePolicyData, verify that they are
+ * moved to ActiveAdmin.
+ */
+ @Test
+ public void testUserControlDisabledPackagesFromR() throws Exception {
+ final String oldTag = "protected-packages";
+ final String newTag = "protected_packages";
+ final int ownerUser = 0;
+ mProvider.mUsers = new int[]{0};
+ getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM);
+ writeVersionToXml(2);
+ preparePoliciesFile(ownerUser, "protected_packages_device_policies.xml");
+ prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml");
+
+ // Validate the setup.
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), oldTag)).isTrue();
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isFalse();
+
+ mUpgrader.upgradePolicy(3);
+
+ assertThat(readVersionFromXml()).isAtLeast(3);
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), oldTag)).isFalse();
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isTrue();
+ }
+
+ /**
+ * In Android S DO protected packages were stored in Owners, verify that they are moved to
+ * ActiveAdmin.
+ */
+ @Test
+ public void testUserControlDisabledPackagesFromS() throws Exception {
+ final String oldTag = "device-owner-protected-packages";
+ final String newTag = "protected_packages";
+ final int ownerUser = 0;
+ mProvider.mUsers = new int[]{0};
+ getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM);
+ writeVersionToXml(2);
+ preparePoliciesFile(ownerUser, "device_policies.xml");
+ prepareDeviceOwnerFile(ownerUser, "protected_packages_device_owner_2.xml");
+
+ // Validate the setup.
+ assertThat(isTagPresent(readDoToStream(), oldTag)).isTrue();
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isFalse();
+
+ mUpgrader.upgradePolicy(3);
+
+ assertThat(readVersionFromXml()).isAtLeast(3);
+ assertThat(isTagPresent(readDoToStream(), oldTag)).isFalse();
+ assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isTrue();
+ }
+
@Test
public void isLatestVersionTested() {
assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION);
@@ -226,32 +281,27 @@
return Integer.parseInt(versionString);
}
- private void preparePoliciesFile(int userId) throws IOException {
+ private void preparePoliciesFile(int userId, String assetFile) throws IOException {
JournaledFile policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId);
DpmTestUtils.writeToFile(
policiesFile.chooseForWrite(),
- DpmTestUtils.readAsset(mRealTestContext,
- "PolicyVersionUpgraderTest/device_policies.xml"));
+ DpmTestUtils.readAsset(mRealTestContext, "PolicyVersionUpgraderTest/" + assetFile));
policiesFile.commit();
}
- private void prepareDeviceOwnerFile(int userId) throws IOException {
- File parentDir = getServices().pathProvider.getDataSystemDirectory();
- File doFilePath = (new File(parentDir, "device_owner_2.xml")).getAbsoluteFile();
- android.util.Log.i("YYYYYY", "DO paath: " + doFilePath);
+ private void prepareDeviceOwnerFile(int userId, String assetFile) throws IOException {
+ File doFilePath = getDoFilePath();
String doFileContent = DpmTestUtils.readAsset(mRealTestContext,
- "PolicyVersionUpgraderTest/device_owner_2.xml")
+ "PolicyVersionUpgraderTest/" + assetFile)
// Substitute the right DO userId, XML in resources has 0
.replace("userId=\"0\"", "userId=\"" + userId + "\"");
DpmTestUtils.writeToFile(doFilePath, doFileContent);
}
- private void prepareProfileOwnerFile(int userId) throws IOException {
- File parentDir = getServices().pathProvider.getUserSystemDirectory(userId);
- DpmTestUtils.writeToFile(
- (new File(parentDir, "profile_owner.xml")).getAbsoluteFile(),
- DpmTestUtils.readAsset(mRealTestContext,
- "PolicyVersionUpgraderTest/profile_owner.xml"));
+ private File getDoFilePath() {
+ File parentDir = getServices().pathProvider.getDataSystemDirectory();
+ File doFilePath = (new File(parentDir, DEVICE_OWNER_XML)).getAbsoluteFile();
+ return doFilePath;
}
private String readPoliciesFile(int userId) throws IOException {
@@ -259,6 +309,10 @@
return new String(Files.asByteSource(policiesFile).read(), Charset.defaultCharset());
}
+ private InputStream readDoToStream() throws IOException {
+ return new FileInputStream(getDoFilePath());
+ }
+
private InputStream readPoliciesFileToStream(int userId) throws IOException {
File policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId).chooseForRead();
return new FileInputStream(policiesFile);
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
new file mode 100644
index 0000000..e78f0c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManagerInternal
+import android.os.IInputConstants
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.InstrumentationRegistry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for {@link InputManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputManagerServiceTests
+ */
+@Presubmit
+class InputManagerServiceTests {
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+
+ @Mock
+ private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+
+ private lateinit var service: InputManagerService
+ private lateinit var localService: InputManagerInternal
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ testLooper = TestLooper()
+ service =
+ InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+ override fun getNativeService(
+ service: InputManagerService?
+ ): NativeInputManagerService {
+ return native
+ }
+
+ override fun registerLocalService(service: InputManagerInternal?) {
+ localService = service!!
+ }
+ })
+ assertTrue("Local service must be registered", this::localService.isInitialized)
+ service.setWindowManagerCallbacks(wmCallbacks)
+ }
+
+ @Test
+ fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
+ val displayId = 123
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+ val viewports = listOf<DisplayViewport>()
+ localService.setDisplayViewports(viewports)
+ verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
+ verify(native).setPointerDisplayId(displayId)
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(displayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ var countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertTrue("Setting virtual pointer display should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+
+ // Ensure that setting the same override again succeeds immediately.
+ assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+
+ // Ensure that we did not query WM for the pointerDisplayId when setting the override
+ verify(wmCallbacks, never()).pointerDisplayId
+
+ // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
+ // pointer displayId and the calling thread is blocked until the native callback happens.
+ countDownLatch = CountDownLatch(1)
+ val pointerDisplayId = 42
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
+ Thread {
+ assertTrue("Unsetting virtual mouse pointer displayId should succeed",
+ localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Unsetting virtual mouse pointer displayId should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(pointerDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ val countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertFalse("Setting virtual pointer display should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates the pointerDisplayId to the incorrect value.
+ service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_competingRequests() {
+ val firstRequestSyncLatch = CountDownLatch(1)
+ doAnswer {
+ firstRequestSyncLatch.countDown()
+ }.`when`(native).setPointerDisplayId(anyInt())
+
+ val firstRequestLatch = CountDownLatch(1)
+ val firstOverride = 123
+ Thread {
+ assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(firstOverride))
+ firstRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ assertTrue("Wait for first thread's request should succeed",
+ firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val secondRequestLatch = CountDownLatch(1)
+ val secondOverride = 42
+ Thread {
+ assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
+ localService.setVirtualMousePointerDisplayId(secondOverride))
+ secondRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual mouse pointer should block",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates directly to the second request.
+ service.onPointerDisplayIdChanged(secondOverride, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
+ assertTrue("Native callback unblocks first thread",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ assertTrue("Native callback unblocks second thread",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native, times(2)).setPointerDisplayId(anyInt())
+ }
+
+ @Test
+ fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+ localService.setVirtualMousePointerDisplayId(10)
+ localService.setPointerIconVisible(false, 10)
+ verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+ localService.setPointerAcceleration(5f, 10)
+ verify(native).setPointerAcceleration(eq(5f))
+
+ service.onDisplayRemoved(10)
+ verify(native).displayRemoved(eq(10))
+ verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+ verify(native).setPointerAcceleration(
+ eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat()))
+
+ localService.setVirtualMousePointerDisplayId(10)
+ verify(native).setPointerDisplayId(eq(10))
+ verifyNoMoreInteractions(native)
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index a227cd3..035249e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -70,6 +70,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -516,6 +517,7 @@
}
}
+ @Ignore("Causing breakages so ignoring to resolve, b/231667368")
@Test
public void initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed() throws Exception {
int uid = Binder.getCallingUid();
@@ -539,6 +541,7 @@
testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint2());
}
+ @Ignore("Causing breakages so ignoring to resolve, b/231667368")
@Test
public void initRecoveryService_updatesCertsIndependentlyForDifferentRoots() throws Exception {
int uid = Binder.getCallingUid();
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 20486b3..8167b44 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -454,14 +454,14 @@
+ " <library \n"
+ " name=\"foo\"\n"
+ " file=\"" + mFooJar + "\"\n"
- + " on-bootclasspath-before=\"Q\"\n"
+ + " on-bootclasspath-before=\"A\"\n"
+ " on-bootclasspath-since=\"W\"\n"
+ " />\n\n"
+ " </permissions>";
parseSharedLibraries(contents);
assertFooIsOnlySharedLibrary();
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
- assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
+ assertThat(entry.onBootclasspathBefore).isEqualTo("A");
assertThat(entry.onBootclasspathSince).isEqualTo("W");
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
index e9515fa..ffc0dcd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -82,8 +82,6 @@
ImportanceExtractor extractor = new ImportanceExtractor();
extractor.setConfig(mConfig);
- when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
- NotificationManager.IMPORTANCE_MIN);
NotificationChannel channel =
new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
@@ -101,8 +99,6 @@
ImportanceExtractor extractor = new ImportanceExtractor();
extractor.setConfig(mConfig);
- when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
- NotificationManager.IMPORTANCE_MIN);
NotificationChannel channel =
new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
index f609306..6f7bace 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
@@ -28,6 +28,13 @@
CallRecord(NotificationChannelEvent event) {
this.event = event;
}
+
+ @Override
+ public String toString() {
+ return "CallRecord{" +
+ "event=" + event +
+ '}';
+ }
}
private List<CallRecord> mCalls = new ArrayList<>();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c0cd7a7..a7dc851 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -52,6 +52,7 @@
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -187,6 +188,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
@@ -220,6 +222,7 @@
import com.google.common.collect.ImmutableList;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -272,6 +275,7 @@
private WindowManagerInternal mWindowManagerInternal;
@Mock
private PermissionHelper mPermissionHelper;
+ private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
private TestableContext mContext = spy(getContext());
private final String PKG = mContext.getPackageName();
private TestableLooper mTestableLooper;
@@ -384,9 +388,6 @@
"android.permission.WRITE_DEVICE_CONFIG",
"android.permission.READ_DEVICE_CONFIG",
"android.permission.READ_CONTACTS");
- Settings.Secure.putIntForUser(
- getContext().getContentResolver(),
- Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM);
MockitoAnnotations.initMocks(this);
@@ -448,6 +449,8 @@
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
+
ActivityManager.AppTask task = mock(ActivityManager.AppTask.class);
List<ActivityManager.AppTask> taskList = new ArrayList<>();
ActivityManager.RecentTaskInfo taskInfo = new ActivityManager.RecentTaskInfo();
@@ -506,7 +509,7 @@
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
- mTelecomManager);
+ mTelecomManager, mLogger);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -582,6 +585,7 @@
assertNotNull(mBinderService.getNotificationChannel(
PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
clearInvocations(mRankingHandler);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
}
@After
@@ -1234,8 +1238,7 @@
@Test
public void testEnqueuedBlockedNotifications_blockedApp() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
mBinderService.enqueueNotificationWithTag(PKG, PKG,
@@ -1248,8 +1251,7 @@
@Test
public void testEnqueuedBlockedNotifications_blockedAppForegroundService() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -1342,6 +1344,30 @@
}
@Test
+ public void testSetNotificationsEnabledForPackage_noChange() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, true);
+
+ verify(mPermissionHelper, never()).setNotificationPermission(
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetNotificationsEnabledForPackage() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
+
+ verify(mPermissionHelper).setNotificationPermission(
+ mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+
+ verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
+ List<NotificationChannelLoggerFake.CallRecord> calls = mLogger.getCalls();
+ Assert.assertEquals(
+ NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED,
+ calls.get(calls.size() -1).event);
+ }
+
+ @Test
public void testBlockedNotifications_blockedByAssistant() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
@@ -1368,7 +1394,6 @@
@Test
public void testBlockedNotifications_blockedByUser() throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
@@ -1377,7 +1402,7 @@
NotificationRecord r = generateNotificationRecord(channel);
mService.addEnqueuedNotification(r);
- when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE);
+ when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
@@ -1390,8 +1415,32 @@
}
@Test
+ public void testEnqueueNotificationInternal_noChannel() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+ NotificationRecord nr = generateNotificationRecord(
+ new NotificationChannel("did not create", "", IMPORTANCE_DEFAULT));
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ verify(mPermissionHelper, never()).hasPermission(Process.SYSTEM_UID);
+
+ reset(mPermissionHelper);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ assertThat(mService.mChannelToastsSent).contains(mUid);
+ }
+
+ @Test
public void testEnqueueNotification_appBlocked() throws Exception {
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotification_appBlocked", 0,
@@ -2686,6 +2735,49 @@
}
@Test
+ public void testDefaultChannelUpdatesApp_postMigrationToPermissions() throws Exception {
+ final NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
+ PKG_N_MR1, ActivityManager.getCurrentUser(), PKG_N_MR1,
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+ defaultChannel.setImportance(IMPORTANCE_NONE);
+
+ mBinderService.updateNotificationChannelForPackage(PKG_N_MR1, mUid, defaultChannel);
+
+ verify(mPermissionHelper).setNotificationPermission(
+ PKG_N_MR1, ActivityManager.getCurrentUser(), false, true);
+ }
+
+ @Test
+ public void testPostNotification_appPermissionFixed() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true);
+
+ NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testPostNotification_appPermissionFixed", 0,
+ temp.getNotification(), 0);
+ waitForIdle();
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(PKG);
+ assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue();
+ }
+
+ @Test
+ public void testSummaryNotification_appPermissionFixed() {
+ NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(temp);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
+
+ NotificationRecord r = mService.createAutoGroupSummary(
+ temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
+
+ assertThat(r.isImportanceFixed()).isTrue();
+ }
+
+ @Test
public void testTvExtenderChannelOverride_onTv() throws Exception {
mService.setIsTelevision(true);
mService.setPreferencesHelper(mPreferencesHelper);
@@ -2718,13 +2810,12 @@
@Test
public void testUpdateAppNotifyCreatorBlock() throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_DEFAULT);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- // should trigger a broadcast
mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
Thread.sleep(500);
waitForIdle();
+
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -2736,7 +2827,7 @@
@Test
public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
@@ -2744,15 +2835,12 @@
@Test
public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
- // should not trigger a broadcast
- when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
-
- // should trigger a broadcast
- mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+ mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
Thread.sleep(500);
waitForIdle();
+
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -4344,7 +4432,7 @@
assertEquals(IMPORTANCE_LOW,
mService.getNotificationRecord(sbn.getKey()).getImportance());
- assertEquals(IMPORTANCE_UNSPECIFIED, mBinderService.getPackageImportance(
+ assertEquals(IMPORTANCE_DEFAULT, mBinderService.getPackageImportance(
sbn.getPackageName()));
nb = new Notification.Builder(mContext)
@@ -4860,6 +4948,7 @@
@Test
public void testBackup() throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
int systemChecks = mService.countSystemChecks;
when(mListeners.queryPackageForServices(anyString(), anyInt(), anyInt()))
.thenReturn(new ArraySet<>());
@@ -5963,8 +6052,7 @@
.thenReturn(false);
// notifications from this package are blocked by the user
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6260,8 +6348,7 @@
.thenReturn(false);
// notifications from this package are blocked by the user
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6347,8 +6434,7 @@
.thenReturn(true);
// notifications from this package are NOT blocked by the user
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
// enqueue toast -> no toasts enqueued
((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
@@ -6369,8 +6455,7 @@
.thenReturn(false);
// notifications from this package are blocked by the user
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
setAppInForegroundForToasts(mUid, false);
@@ -6393,8 +6478,7 @@
.thenReturn(true);
// notifications from this package ARE blocked by the user
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
setAppInForegroundForToasts(mUid, false);
@@ -7303,6 +7387,14 @@
}
@Test
+ public void testAreNotificationsEnabledForPackage() throws Exception {
+ mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+ mUid);
+
+ verify(mPermissionHelper).hasPermission(mUid);
+ }
+
+ @Test
public void testAreNotificationsEnabledForPackage_crossUser() throws Exception {
try {
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
@@ -7311,21 +7403,31 @@
} catch (SecurityException e) {
// pass
}
+ verify(mPermissionHelper, never()).hasPermission(anyInt());
// cross user, with permission, no problem
enableInteractAcrossUsers();
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid + UserHandle.PER_USER_RANGE);
- verify(mPermissionHelper, never()).hasPermission(anyInt());
+ verify(mPermissionHelper).hasPermission(mUid + UserHandle.PER_USER_RANGE);
}
@Test
- public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception {
- assertEquals(mInternalService.areNotificationsEnabledForPackage(
- mContext.getPackageName(), mUid),
- mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid));
- verify(mPermissionHelper, never()).hasPermission(anyInt());
+ public void testAreNotificationsEnabledForPackage_viaInternalService() {
+ mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid);
+ verify(mPermissionHelper).hasPermission(mUid);
+ }
+
+ @Test
+ public void testGetPackageImportance() throws Exception {
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+ assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
+ .isEqualTo(IMPORTANCE_DEFAULT);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+ assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
+ .isEqualTo(IMPORTANCE_NONE);
}
@Test
@@ -8975,48 +9077,13 @@
}
@Test
- public void testMigrationDisabledByDefault() {
- assertThat(mService.mEnableAppSettingMigration).isFalse();
- }
-
- @Test
- public void testPostNotification_channelLockedFixed() throws Exception {
- mTestNotificationChannel.setImportanceLockedByOEM(true);
-
- NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
- waitForIdle();
- assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
- StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(PKG);
- assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue();
-
- mBinderService.cancelAllNotifications(PKG, 0);
- waitForIdle();
-
- mTestNotificationChannel.setImportanceLockedByOEM(false);
- mTestNotificationChannel.setImportanceLockedByCriticalDeviceFunction(true);
-
- temp = generateNotificationRecord(mTestNotificationChannel);
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
- waitForIdle();
- assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
- notifs = mBinderService.getActiveNotifications(PKG);
- assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue();
- }
-
- @Test
public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_NONE);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList())
.isEmpty();
- verify(mPermissionHelper, never()).hasPermission(anyInt());
verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid);
}
@@ -9110,8 +9177,7 @@
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mBinderService.setNotificationsEnabledForPackage(
- r.getSbn().getPackageName(), r.getUid(), false);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
// normal blocked notifications - blocked
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
@@ -9149,6 +9215,67 @@
}
@Test
+ public void testMediaNotificationsBypassBlock_atPost() throws Exception {
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "test", null).build());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
+
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+
+ // just using the style - blocked
+ mService.clearNotifications();
+ reset(mUsageStats);
+ nb.setStyle(new Notification.MediaStyle());
+ sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+
+ // style + media session - bypasses block
+ mService.clearNotifications();
+ reset(mUsageStats);
+ nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
+ sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats, never()).registerBlocked(any());
+ verify(mUsageStats).registerPostedByApp(any());
+ }
+
+ @Test
public void testCallNotificationsBypassBlock() throws Exception {
when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
.thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
@@ -9163,8 +9290,7 @@
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mBinderService.setNotificationsEnabledForPackage(
- r.getSbn().getPackageName(), r.getUid(), false);
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
// normal blocked notifications - blocked
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
@@ -9194,12 +9320,151 @@
r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+
+ // set telecom manager to null - blocked
+ mService.setTelecomManager(null);
+ assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+ r.getSbn().getId(), r.getSbn().getTag(), r, false))
+ .isFalse();
+
+ // set telecom feature to false - blocked
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false);
+ assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+ r.getSbn().getId(), r.getSbn().getTag(), r, false))
+ .isFalse();
}
@Test
- public void testGetAllUsersNotificationPermissions_migrationNotEnabled() {
- // make sure we don't bother if the migration is not enabled
- assertThat(mService.getAllUsersNotificationPermissions()).isNull();
+ public void testCallNotificationsBypassBlock_atPost() throws Exception {
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+
+ Notification.Builder nb =
+ new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "test", null).build());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+
+ // normal blocked notifications - blocked
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+
+ // just using the style - blocked
+ mService.clearNotifications();
+ reset(mUsageStats);
+ Person person = new Person.Builder().setName("caller").build();
+ nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
+ nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, nb.build(),
+ UserHandle.getUserHandleForUid(mUid), null, 0);
+ r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ runnable = mService.new PostNotificationRunnable(
+ r.getKey(), r.getSbn().getPackageName(), r.getUid(), SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+
+ // style + managed call - bypasses block
+ mService.clearNotifications();
+ reset(mUsageStats);
+ when(mTelecomManager.isInManagedCall()).thenReturn(true);
+
+ mService.addEnqueuedNotification(r);
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats, never()).registerBlocked(any());
+ verify(mUsageStats).registerPostedByApp(any());
+
+ // style + self managed call - bypasses block
+ mService.clearNotifications();
+ reset(mUsageStats);
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser()))
+ .thenReturn(true);
+
+ mService.addEnqueuedNotification(r);
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats, never()).registerBlocked(any());
+ verify(mUsageStats).registerPostedByApp(any());
+
+ // set telecom manager to null - notifications should be blocked
+ // but post notifications runnable should not crash
+ mService.clearNotifications();
+ reset(mUsageStats);
+ mService.setTelecomManager(null);
+
+ mService.addEnqueuedNotification(r);
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+
+ // set FEATURE_TELECOM to false - notifications should be blocked
+ // but post notifications runnable should not crash
+ mService.setTelecomManager(mTelecomManager);
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false);
+ reset(mUsageStats);
+ mService.setTelecomManager(null);
+
+ mService.addEnqueuedNotification(r);
+ runnable.run();
+ waitForIdle();
+
+ verify(mUsageStats).registerBlocked(any());
+ verify(mUsageStats, never()).registerPostedByApp(any());
+ }
+
+ @Test
+ public void testGetAllUsersNotificationPermissions() {
+ // In this case, there are multiple users each with notification permissions (and also,
+ // for good measure, some without).
+ // make sure the collection returned contains info for all of them
+ final List<UserInfo> userInfos = new ArrayList<>();
+ userInfos.add(new UserInfo(0, "user0", 0));
+ userInfos.add(new UserInfo(1, "user1", 0));
+ userInfos.add(new UserInfo(2, "user2", 0));
+ when(mUm.getUsers()).thenReturn(userInfos);
+
+ // construct the permissions for each of them
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(),
+ permissions1 = new ArrayMap<>();
+ permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false));
+ permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true));
+ permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false));
+ permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true));
+ when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0);
+ when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1);
+ when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>());
+
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions =
+ mService.getAllUsersNotificationPermissions();
+ assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first);
+ assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second);
+ assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first);
+ assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second);
+ assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first);
+ assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second);
+ assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first);
+ assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second);
}
@Test
@@ -9308,6 +9573,8 @@
@Test
public void testMaybeShowReviewPermissionsNotification_unknown() {
+ reset(mMockNm);
+
// Set up various possible states of the settings int and confirm whether or not the
// notification is shown as expected
@@ -9321,6 +9588,8 @@
@Test
public void testMaybeShowReviewPermissionsNotification_shouldShow() {
+ reset(mMockNm);
+
// If state is SHOULD_SHOW, it ... should show
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
@@ -9333,6 +9602,8 @@
@Test
public void testMaybeShowReviewPermissionsNotification_alreadyShown() {
+ reset(mMockNm);
+
// If state is either USER_INTERACTED or DISMISSED, we should not show this on boot
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
@@ -9349,6 +9620,8 @@
@Test
public void testMaybeShowReviewPermissionsNotification_reshown() {
+ reset(mMockNm);
+
// If we have re-shown the notification and the user did not subsequently interacted with
// it, then make sure we show when trying on boot
Settings.Global.putInt(mContext.getContentResolver(),
@@ -9362,6 +9635,8 @@
@Test
public void testRescheduledReviewPermissionsNotification() {
+ reset(mMockNm);
+
// when rescheduled, the notification goes through the NotificationManagerInternal service
// this call doesn't need to know anything about previously scheduled state -- if called,
// it should send the notification & write the appropriate int to Settings
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
deleted file mode 100755
index b751c7f..0000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ /dev/null
@@ -1,877 +0,0 @@
-/*
- * Copyright (C) 2021 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.notification;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.PendingIntent.FLAG_MUTABLE;
-import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.INotificationManager;
-import android.app.IUriGrantsManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.StatsManager;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.app.usage.UsageStatsManagerInternal;
-import android.companion.ICompanionDeviceManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutServiceInternal;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.session.MediaSession;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerFilter;
-import android.service.notification.StatusBarNotification;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.testing.TestablePermissions;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Pair;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.InstanceIdSequenceFake;
-import com.android.server.DeviceIdleInternal;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.UiServiceTestCase;
-import com.android.server.lights.LightsManager;
-import com.android.server.lights.LogicalLight;
-import com.android.server.notification.NotificationManagerService.NotificationAssistants;
-import com.android.server.notification.NotificationManagerService.NotificationListeners;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.server.utils.quota.MultiRateLimiter;
-import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.WindowManagerInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-/**
- * Tests that NMS reads/writes the app notification state from Package/PermissionManager when
- * migration is enabled. Because the migration field is read onStart
- * TODO (b/194833441): migrate these tests to NotificationManagerServiceTest when the migration is
- * permanently enabled.
- */
-public class NotificationPermissionMigrationTest extends UiServiceTestCase {
- private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
- private static final int UID_HEADLESS = 1000000;
-
- private final int mUid = Binder.getCallingUid();
- private TestableNotificationManagerService mService;
- private INotificationManager mBinderService;
- private NotificationManagerInternal mInternalService;
- private ShortcutHelper mShortcutHelper;
- @Mock
- private IPackageManager mPackageManager;
- @Mock
- private PackageManager mPackageManagerClient;
- @Mock
- private PackageManagerInternal mPackageManagerInternal;
- @Mock
- private WindowManagerInternal mWindowManagerInternal;
- @Mock
- private PermissionHelper mPermissionHelper;
- private TestableContext mContext = spy(getContext());
- private final String PKG = mContext.getPackageName();
- private TestableLooper mTestableLooper;
- @Mock
- private RankingHelper mRankingHelper;
- @Mock private PreferencesHelper mPreferencesHelper;
- AtomicFile mPolicyFile;
- File mFile;
- @Mock
- private NotificationUsageStats mUsageStats;
- @Mock
- private UsageStatsManagerInternal mAppUsageStats;
- @Mock
- private AudioManager mAudioManager;
- @Mock
- private LauncherApps mLauncherApps;
- @Mock
- private ShortcutServiceInternal mShortcutServiceInternal;
- @Mock
- private UserManager mUserManager;
- @Mock
- ActivityManager mActivityManager;
- @Mock
- Resources mResources;
- @Mock
- RankingHandler mRankingHandler;
- @Mock
- ActivityManagerInternal mAmi;
- @Mock
- private Looper mMainLooper;
-
- @Mock
- IIntentSender pi1;
-
- private static final int MAX_POST_DELAY = 1000;
-
- private NotificationChannel mTestNotificationChannel = new NotificationChannel(
- TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
-
- private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
-
- @Mock
- private NotificationListeners mListeners;
- @Mock
- private NotificationListenerFilter mNlf;
- @Mock private NotificationAssistants mAssistants;
- @Mock private ConditionProviders mConditionProviders;
- private ManagedServices.ManagedServiceInfo mListener;
- @Mock private ICompanionDeviceManager mCompanionMgr;
- @Mock SnoozeHelper mSnoozeHelper;
- @Mock GroupHelper mGroupHelper;
- @Mock
- IBinder mPermOwner;
- @Mock
- IActivityManager mAm;
- @Mock
- ActivityTaskManagerInternal mAtm;
- @Mock
- IUriGrantsManager mUgm;
- @Mock
- UriGrantsManagerInternal mUgmInternal;
- @Mock
- AppOpsManager mAppOpsManager;
- @Mock
- private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
- mNotificationAssistantAccessGrantedCallback;
- @Mock
- UserManager mUm;
- @Mock
- NotificationHistoryManager mHistoryManager;
- @Mock
- StatsManager mStatsManager;
- @Mock
- AlarmManager mAlarmManager;
- @Mock
- MultiRateLimiter mToastRateLimiter;
- BroadcastReceiver mPackageIntentReceiver;
- NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
- private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
- 1 << 30);
- @Mock
- StatusBarManagerInternal mStatusBar;
-
- private NotificationManagerService.WorkerHandler mWorkerHandler;
-
- @Before
- public void setUp() throws Exception {
- // These should be the only difference in setup from NMSTest
- Settings.Secure.putIntForUser(
- getContext().getContentResolver(),
- Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 1, USER_SYSTEM);
- Settings.Global.putInt(getContext().getContentResolver(),
- Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 1);
-
- // Shell permissions will override permissions of our app, so add all necessary permissions
- // for this test here:
- InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- "android.permission.WRITE_DEVICE_CONFIG",
- "android.permission.READ_DEVICE_CONFIG",
- "android.permission.READ_CONTACTS");
-
- MockitoAnnotations.initMocks(this);
-
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
-
- DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
- when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
-
- LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
- LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
- LocalServices.removeServiceForTest(WindowManagerInternal.class);
- LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
- LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
- LocalServices.addService(StatusBarManagerInternal.class, mStatusBar);
- LocalServices.removeServiceForTest(DeviceIdleInternal.class);
- LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.addService(ActivityManagerInternal.class, mAmi);
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
- mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
-
- doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
-
- mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
- mNotificationInstanceIdSequence);
-
- // Use this testable looper.
- mTestableLooper = TestableLooper.get(this);
- // MockPackageManager - default returns ApplicationInfo with matching calling UID
- mContext.setMockPackageManager(mPackageManagerClient);
-
- when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt()))
- .thenAnswer((Answer<ApplicationInfo>) invocation -> {
- Object[] args = invocation.getArguments();
- return getApplicationInfo((String) args[0], mUid);
- });
- when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenAnswer((Answer<ApplicationInfo>) invocation -> {
- Object[] args = invocation.getArguments();
- return getApplicationInfo((String) args[0], mUid);
- });
- when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
- when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenAnswer(
- (Answer<Boolean>) invocation -> {
- Object[] args = invocation.getArguments();
- return (int) args[1] == mUid;
- });
- final LightsManager mockLightsManager = mock(LightsManager.class);
- when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
- when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
- when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG});
- when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG});
- mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
-
- // write to a test file; the system file isn't readable from tests
- mFile = new File(mContext.getCacheDir(), "test.xml");
- mFile.createNewFile();
- final String preupgradeXml = "<notification-policy></notification-policy>";
- mPolicyFile = new AtomicFile(mFile);
- FileOutputStream fos = mPolicyFile.startWrite();
- fos.write(preupgradeXml.getBytes());
- mPolicyFile.finishWrite(fos);
-
- // Setup managed services
- when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
- when(mNlf.isPackageAllowed(any())).thenReturn(true);
- when(mNlf.isPackageAllowed(null)).thenReturn(true);
- when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
- mListener = mListeners.new ManagedServiceInfo(
- null, new ComponentName(PKG, "test_class"),
- UserHandle.getUserId(mUid), true, null, 0, 123);
- ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
- ArraySet<ComponentName> components = new ArraySet<>();
- components.add(defaultComponent);
- when(mListeners.getDefaultComponents()).thenReturn(components);
- when(mConditionProviders.getDefaultPackages())
- .thenReturn(new ArraySet<>(Arrays.asList("config")));
- when(mAssistants.getDefaultComponents()).thenReturn(components);
- when(mAssistants.queryPackageForServices(
- anyString(), anyInt(), anyInt())).thenReturn(components);
- when(mListeners.checkServiceTokenLocked(null)).thenReturn(mListener);
- ManagedServices.Config listenerConfig = new ManagedServices.Config();
- listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS;
- when(mListeners.getConfig()).thenReturn(listenerConfig);
- ManagedServices.Config assistantConfig = new ManagedServices.Config();
- assistantConfig.xmlTag = NotificationAssistants.TAG_ENABLED_NOTIFICATION_ASSISTANTS;
- when(mAssistants.getConfig()).thenReturn(assistantConfig);
- ManagedServices.Config dndConfig = new ManagedServices.Config();
- dndConfig.xmlTag = ConditionProviders.TAG_ENABLED_DND_APPS;
- when(mConditionProviders.getConfig()).thenReturn(dndConfig);
-
- when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
-
- // apps allowed as convos
- mService.setStringArrayResourceValue(PKG_O);
-
- mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
- mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
- mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
- mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
- mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
- mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
- mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper,
- mock(UsageStatsManagerInternal.class), mock(TelecomManager.class));
- // Return first true for RoleObserver main-thread check
- when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
-
- mService.setAudioManager(mAudioManager);
-
- mShortcutHelper = mService.getShortcutHelper();
- mShortcutHelper.setLauncherApps(mLauncherApps);
- mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal);
- mShortcutHelper.setUserManager(mUserManager);
-
- // Capture PackageIntentReceiver
- ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
- ArgumentCaptor<IntentFilter> intentFilterCaptor =
- ArgumentCaptor.forClass(IntentFilter.class);
-
- verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(),
- any(), intentFilterCaptor.capture(), any(), any());
- verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(),
- intentFilterCaptor.capture());
- List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
- List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
-
- for (int i = 0; i < intentFilters.size(); i++) {
- final IntentFilter filter = intentFilters.get(i);
- if (filter.hasAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)
- && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
- && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
- mPackageIntentReceiver = broadcastReceivers.get(i);
- }
- }
- assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
-
- // Pretend the shortcut exists
- List<ShortcutInfo> shortcutInfos = new ArrayList<>();
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(PKG);
- when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- shortcutInfos.add(info);
- when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
- when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
- anyString(), anyInt(), any())).thenReturn(true);
- when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
-
- // Set the testable bubble extractor
- RankingHelper rankingHelper = mService.getRankingHelper();
- BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
- extractor.setActivityManager(mActivityManager);
-
- // Tests call directly into the Binder.
- mBinderService = mService.getBinderService();
- mInternalService = mService.getInternalService();
-
- mBinderService.createNotificationChannels(
- PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
- mBinderService.createNotificationChannels(
- PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
- mBinderService.createNotificationChannels(
- PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
- assertNotNull(mBinderService.getNotificationChannel(
- PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
- clearInvocations(mRankingHandler);
- }
-
- @After
- public void tearDown() throws Exception {
- if (mFile != null) mFile.delete();
-
- try {
- mService.onDestroy();
- } catch (IllegalStateException | IllegalArgumentException e) {
- // can throw if a broadcast receiver was never registered
- }
-
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation().dropShellPermissionIdentity();
- // Remove scheduled messages that would be processed when the test is already done, and
- // could cause issues, for example, messages that remove/cancel shown toasts (this causes
- // problematic interactions with mocks when they're no longer working as expected).
- mWorkerHandler.removeCallbacksAndMessages(null);
- }
-
- private ApplicationInfo getApplicationInfo(String pkg, int uid) {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.uid = uid;
- switch (pkg) {
- case PKG_N_MR1:
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- break;
- case PKG_O:
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
- break;
- case PKG_P:
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
- break;
- default:
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- break;
- }
- return applicationInfo;
- }
-
- public void waitForIdle() {
- mTestableLooper.processAllMessages();
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
- return generateNotificationRecord(channel, null);
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel,
- Notification.TvExtender extender) {
- if (channel == null) {
- channel = mTestNotificationChannel;
- }
- Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
- if (extender != null) {
- nb.extend(extender);
- }
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- return new NotificationRecord(mContext, sbn, channel);
- }
-
- private void enableInteractAcrossUsers() {
- TestablePermissions perms = mContext.getTestablePermissions();
- perms.setPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, PERMISSION_GRANTED);
- }
-
- @Test
- public void testAreNotificationsEnabledForPackage() throws Exception {
- mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
- mUid);
-
- verify(mPermissionHelper).hasPermission(mUid);
- }
-
- @Test
- public void testAreNotificationsEnabledForPackage_crossUser() throws Exception {
- try {
- mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
- mUid + UserHandle.PER_USER_RANGE);
- fail("Cannot call cross user without permission");
- } catch (SecurityException e) {
- // pass
- }
- verify(mPermissionHelper, never()).hasPermission(anyInt());
-
- // cross user, with permission, no problem
- enableInteractAcrossUsers();
- mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
- mUid + UserHandle.PER_USER_RANGE);
-
- verify(mPermissionHelper).hasPermission(mUid + UserHandle.PER_USER_RANGE);
- }
-
- @Test
- public void testAreNotificationsEnabledForPackage_viaInternalService() {
- mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid);
- verify(mPermissionHelper).hasPermission(mUid);
- }
-
- @Test
- public void testGetPackageImportance() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
- .isEqualTo(IMPORTANCE_DEFAULT);
-
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
- assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
- .isEqualTo(IMPORTANCE_NONE);
- }
-
- @Test
- public void testEnqueueNotificationInternal_noChannel() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
- NotificationRecord nr = generateNotificationRecord(
- new NotificationChannel("did not create", "", IMPORTANCE_DEFAULT));
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- verify(mPermissionHelper).hasPermission(mUid);
- verify(mPermissionHelper, never()).hasPermission(Process.SYSTEM_UID);
-
- reset(mPermissionHelper);
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- verify(mPermissionHelper).hasPermission(mUid);
- assertThat(mService.mChannelToastsSent).contains(mUid);
- }
-
- @Test
- public void testSetNotificationsEnabledForPackage_noChange() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, true);
-
- verify(mPermissionHelper, never()).setNotificationPermission(
- anyString(), anyInt(), anyBoolean(), anyBoolean());
- }
-
- @Test
- public void testSetNotificationsEnabledForPackage() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
-
- verify(mPermissionHelper).setNotificationPermission(
- mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
-
- verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
- }
-
- @Test
- public void testUpdateAppNotifyCreatorBlock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
- public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
- mService.setPreferencesHelper(mPreferencesHelper);
-
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList())
- .isEmpty();
- verify(mPreferencesHelper, never()).getImportance(anyString(), anyInt());
- verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid);
- }
-
- @Test
- public void testBlockedNotifications_blockedByUser() throws Exception {
- when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
- when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
-
- NotificationChannel channel = new NotificationChannel("id", "name",
- NotificationManager.IMPORTANCE_HIGH);
- NotificationRecord r = generateNotificationRecord(channel);
- mService.addEnqueuedNotification(r);
-
- when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
-
- NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
- runnable.run();
- waitForIdle();
-
- verify(mUsageStats).registerBlocked(any());
- verify(mUsageStats, never()).registerPostedByApp(any());
- }
-
- @Test
- public void testEnqueueNotification_appBlocked() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "testEnqueueNotification_appBlocked", 0,
- generateNotificationRecord(null).getNotification(), 0);
- waitForIdle();
- verify(mWorkerHandler, never()).post(
- any(NotificationManagerService.EnqueueNotificationRunnable.class));
- }
-
- @Test
- public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception {
- final NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
- PKG_N_MR1, ActivityManager.getCurrentUser(), PKG_N_MR1,
- NotificationChannel.DEFAULT_CHANNEL_ID);
- defaultChannel.setImportance(IMPORTANCE_NONE);
-
- mBinderService.updateNotificationChannelForPackage(PKG_N_MR1, mUid, defaultChannel);
-
- verify(mPermissionHelper).setNotificationPermission(
- PKG_N_MR1, ActivityManager.getCurrentUser(), false, true);
- }
-
- @Test
- public void testPostNotification_appPermissionFixed() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true);
-
- NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
- waitForIdle();
- assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
- StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(PKG);
- assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue();
- }
-
- @Test
- public void testSummaryNotification_appPermissionFixed() {
- NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
- mService.addNotification(temp);
-
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
-
- NotificationRecord r = mService.createAutoGroupSummary(
- temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
-
- assertThat(r.isImportanceFixed()).isTrue();
- }
-
- @Test
- public void testMediaNotificationsBypassBlock() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
- when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
-
- Notification.Builder nb = new Notification.Builder(
- mContext, mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- // normal blocked notifications - blocked
- assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
- r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
-
- // just using the style - blocked
- nb.setStyle(new Notification.MediaStyle());
- sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
- r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
-
- // using the style, but incorrect type in session - blocked
- nb.setStyle(new Notification.MediaStyle());
- Bundle extras = new Bundle();
- extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent());
- nb.addExtras(extras);
- sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
- r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
-
- // style + media session - bypasses block
- nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
- sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
- r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
- }
-
- @Test
- public void testMediaNotificationsBypassBlock_atPost() throws Exception {
- when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
- when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
-
- Notification.Builder nb = new Notification.Builder(
- mContext, mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
-
- mService.addEnqueuedNotification(r);
- NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
- runnable.run();
- waitForIdle();
-
- verify(mUsageStats).registerBlocked(any());
- verify(mUsageStats, never()).registerPostedByApp(any());
-
- // just using the style - blocked
- mService.clearNotifications();
- reset(mUsageStats);
- nb.setStyle(new Notification.MediaStyle());
- sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- mService.addEnqueuedNotification(r);
- runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
- runnable.run();
- waitForIdle();
-
- verify(mUsageStats).registerBlocked(any());
- verify(mUsageStats, never()).registerPostedByApp(any());
-
- // style + media session - bypasses block
- mService.clearNotifications();
- reset(mUsageStats);
- nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
- sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- mService.addEnqueuedNotification(r);
- runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
- runnable.run();
- waitForIdle();
-
- verify(mUsageStats, never()).registerBlocked(any());
- verify(mUsageStats).registerPostedByApp(any());
- }
-
- @Test
- public void testGetAllUsersNotificationPermissions() {
- // In this case, there are multiple users each with notification permissions (and also,
- // for good measure, some without).
- // make sure the collection returned contains info for all of them
- final List<UserInfo> userInfos = new ArrayList<>();
- userInfos.add(new UserInfo(0, "user0", 0));
- userInfos.add(new UserInfo(1, "user1", 0));
- userInfos.add(new UserInfo(2, "user2", 0));
- when(mUm.getUsers()).thenReturn(userInfos);
-
- // construct the permissions for each of them
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(),
- permissions1 = new ArrayMap<>();
- permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false));
- permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true));
- permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false));
- permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true));
- when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0);
- when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1);
- when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>());
-
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions =
- mService.getAllUsersNotificationPermissions();
- assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first);
- assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second);
- assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first);
- assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second);
- assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first);
- assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second);
- assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first);
- assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second);
- }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index d89141c..5468220 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -1065,9 +1065,8 @@
}
@Test
- public void testApplyImportanceAdjustmentsForNonOemDefaultAppLockedChannels() {
+ public void testApplyImportanceAdjustments() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
- channel.setImportanceLockedByOEM(false);
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 46b47f4..3a352cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -88,51 +88,13 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, false);
+ mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, false);
PackageInfo testPkgInfo = new PackageInfo();
testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS };
when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
.thenReturn(testPkgInfo);
}
- // TODO (b/194833441): Remove when the migration is enabled
- @Test
- public void testMethodsThrowIfMigrationDisabled() throws IllegalAccessException,
- InvocationTargetException {
- PermissionHelper permHelper =
- new PermissionHelper(mPmi, mPackageManager, mPermManager, false, false);
-
- Method[] allMethods = PermissionHelper.class.getDeclaredMethods();
- for (Method method : allMethods) {
- if (Modifier.isPublic(method.getModifiers()) &&
- !Objects.equals("isMigrationEnabled", method.getName())) {
- Parameter[] params = method.getParameters();
- List<Object> args = Lists.newArrayListWithCapacity(params.length);
- for (int i = 0; i < params.length; i++) {
- Type type = params[i].getParameterizedType();
- if (type.getTypeName().equals("java.lang.String")) {
- args.add("");
- } else if (type.getTypeName().equals("boolean")){
- args.add(false);
- } else if (type.getTypeName().equals("int")) {
- args.add(1);
- } else if (type.getTypeName().equals(
- "com.android.server.notification.PermissionHelper$PackagePermission")) {
- args.add(null);
- }
- }
- try {
- method.invoke(permHelper, args.toArray());
- fail("Method should have thrown because migration flag is disabled");
- } catch (InvocationTargetException e) {
- if (!(e.getTargetException() instanceof IllegalStateException)) {
- throw e;
- }
- }
- }
- }
- }
-
@Test
public void testHasPermission() throws Exception {
when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
@@ -304,7 +266,7 @@
@Test
public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet()
throws Exception {
- mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, true);
+ mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true);
when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
.thenReturn(PERMISSION_DENIED);
when(mPermManager.getPermissionFlags(anyString(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 6d08959..a5cec7e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -278,6 +278,14 @@
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT);
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(true, false));
+ appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
@@ -408,6 +416,13 @@
NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(uid0, package0), new Pair(false, false));
+ appPermissions.put(new Pair(uid10, package10), new Pair(true, false));
+
+ when(mPermissionHelper.getNotificationPermissionValues(10))
+ .thenReturn(appPermissions);
+
ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
// Reset state.
@@ -433,6 +448,12 @@
NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(uid0, package0), new Pair(true, false));
+
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
// Reset state.
@@ -478,7 +499,6 @@
assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false));
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
- mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
UserHandle.USER_ALL, channel1.getId(), channel2.getId(),
@@ -489,7 +509,6 @@
loadStreamXml(baos, false, UserHandle.USER_ALL);
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));
assertEquals(channel1,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
@@ -550,8 +569,6 @@
mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
mHelper.setValidBubbleSent(PKG_P, UID_P);
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
-
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
@@ -562,7 +579,6 @@
loadStreamXml(baos, true, USER_SYSTEM);
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O));
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P));
assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1));
@@ -601,7 +617,6 @@
@Test
public void testReadXml_oldXml_migrates() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -672,7 +687,6 @@
@Test
public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -751,7 +765,6 @@
@Test
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -809,7 +822,6 @@
@Test
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -866,7 +878,6 @@
@Test
public void testReadXml_oldXml_migration_NoUid() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -900,7 +911,6 @@
@Test
public void testReadXml_newXml_noMigration_NoUid() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -933,7 +943,6 @@
@Test
public void testChannelXmlForNonBackup_postMigration() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -1014,7 +1023,6 @@
@Test
public void testChannelXmlForBackup_postMigration() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -1101,7 +1109,6 @@
@Test
public void testChannelXmlForBackup_postMigration_noExternal() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -1181,7 +1188,6 @@
@Test
public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -1303,6 +1309,12 @@
@Test
public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
channel.setSound(null, mAudioAttributes);
@@ -1472,14 +1484,6 @@
}
@Test
- public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE);
-
- assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false));
- }
-
- @Test
public void testCreateChannel_badImportance() throws Exception {
try {
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
@@ -1543,12 +1547,10 @@
@Test
public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
- mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_UNSPECIFIED);
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
- assertFalse(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));
NotificationChannel defaultChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
@@ -1566,8 +1568,6 @@
assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
UID_N_MR1));
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));
}
@Test
@@ -1592,9 +1592,6 @@
assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG_O, UID_O));
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O,
- UID_O));
- assertFalse(mHelper.getIsAppImportanceLocked(PKG_O, UID_O));
}
@Test
@@ -1629,8 +1626,6 @@
assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
- UID_N_MR1));
}
@Test
@@ -2015,8 +2010,9 @@
}
@Test
- public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() throws Exception {
+ public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
// create notification channel that can't bypass dnd
// expected result: areChannelsBypassingDnd = false
@@ -2029,7 +2025,6 @@
// create notification channel that can bypass dnd
// expected result: areChannelsBypassingDnd = true
- assertTrue(mHelper.getImportance(PKG_N_MR1, uid) != IMPORTANCE_NONE);
NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
@@ -2052,8 +2047,6 @@
@Test
public void testCreateAndUpdateChannelsBypassingDnd_permissionHelper() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
-
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
// create notification channel that can't bypass dnd
@@ -2076,10 +2069,8 @@
}
@Test
- public void testCreateAndDeleteCanChannelsBypassDnd_permissionHelper() throws Exception {
+ public void testCreateAndDeleteCanChannelsBypassDnd_permissionHelper() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
-
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
// create notification channel that can't bypass dnd
@@ -2113,8 +2104,9 @@
}
@Test
- public void testBlockedGroupDoesNotBypassDnd() throws Exception {
+ public void testBlockedGroupDoesNotBypassDnd() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
@@ -2140,33 +2132,10 @@
}
@Test
- public void testBlockedAppsDoNotBypassDnd_localSettings() throws Exception {
+ public void testBlockedAppsDoNotBypassDnd_localSettings() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
-
- // start in a 'allowed to bypass dnd state'
- mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
- mAppOpsManager, mStatsEventBuilderFactory);
-
- mHelper.setImportance(PKG_N_MR1, uid, IMPORTANCE_NONE);
- // create notification channel that can bypass dnd, but app is blocked
- // expected result: areChannelsBypassingDnd = false
- NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testBlockedAppsDoNotBypassDnd_permissionHelper() throws Exception {
- int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.hasPermission(uid)).thenReturn(false);
+
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
@@ -2186,8 +2155,32 @@
}
@Test
- public void testUpdateCanChannelsBypassDnd() throws Exception {
+ public void testBlockedAppsDoNotBypassDnd_permissionHelper() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(false);
+
+ // start in a 'allowed to bypass dnd state'
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ // create notification channel that can bypass dnd, but app is blocked
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testUpdateCanChannelsBypassDnd() {
+ int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
+ when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
// create notification channel that can't bypass dnd
// expected result: areChannelsBypassingDnd = false
@@ -2405,8 +2398,8 @@
when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);
// create records with the default channel for all user 0 and user 1 uids
- mHelper.getImportance(PKG_N_MR1, user0Uids[i]);
- mHelper.getImportance(PKG_N_MR1, user1Uids[i]);
+ mHelper.canShowBadge(PKG_N_MR1, user0Uids[i]);
+ mHelper.canShowBadge(PKG_N_MR1, user1Uids[i]);
}
mHelper.onUserRemoved(1);
@@ -2445,17 +2438,6 @@
}
@Test
- public void testOnPackageChanged_packageRemoval_importance() throws Exception {
- mHelper.setImportance(PKG_N_MR1, UID_N_MR1, NotificationManager.IMPORTANCE_HIGH);
-
- mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
- UID_N_MR1});
-
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
- UID_N_MR1));
- }
-
- @Test
public void testOnPackageChanged_packageRemoval_groups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -2496,17 +2478,14 @@
mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
mHelper.createNotificationChannelGroup(
PKG_O, UID_O, new NotificationChannelGroup("1", "bye"), true);
- mHelper.lockChannelsForOEM(pkg.toArray(new String[]{}));
mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, pkgPair);
mHelper.setNotificationDelegate(PKG_O, UID_O, "", 1);
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
mHelper.setBubblesAllowed(PKG_O, UID_O, DEFAULT_BUBBLE_PREFERENCE);
mHelper.setShowBadge(PKG_O, UID_O, false);
mHelper.setAppImportanceLocked(PKG_O, UID_O);
mHelper.clearData(PKG_O, UID_O);
- assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O, UID_O));
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), DEFAULT_BUBBLE_PREFERENCE);
assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
@@ -2518,13 +2497,10 @@
mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
assertTrue(channel.isImportanceLockedByCriticalDeviceFunction());
- assertTrue(channel.isImportanceLockedByOEM());
}
@Test
public void testRecordDefaults() throws Exception {
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
- UID_N_MR1));
assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
}
@@ -2760,69 +2736,7 @@
}
@Test
- public void testDumpJson_prePermissionMigration() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
- // before the migration is active, we want to verify that:
- // - all notification importance info should come from package preferences
- // - if there are permissions granted or denied from packages PreferencesHelper doesn't
- // know about, those are ignored if migration is not enabled
-
- // package permissions map to be passed in
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
- appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
- appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); // in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
-
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel3 = new NotificationChannel("id3", "name3", IMPORTANCE_HIGH);
-
- mHelper.createNotificationChannel(PKG_P, UID_P, channel1, true, false);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false);
- mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE);
- mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
-
- // in the json array, all of the individual package preferences are simply elements in the
- // values array. this set is to collect expected outputs for each of our packages.
- // the key/value pairs are: (userId, package name) -> expected importance
- ArrayMap<Pair<Integer, String>, String> expected = new ArrayMap<>();
- expected.put(new Pair(UserHandle.getUserId(UID_P), PKG_P), "LOW");
- expected.put(new Pair(UserHandle.getUserId(UID_O), PKG_O), "HIGH");
- expected.put(new Pair(UserHandle.getUserId(UID_N_MR1), PKG_N_MR1), "NONE");
-
- JSONArray actual = (JSONArray) mHelper.dumpJson(
- new NotificationManagerService.DumpFilter(), appPermissions)
- .get("PackagePreferencess");
- assertThat(actual.length()).isEqualTo(expected.size());
- for (int i = 0; i < actual.length(); i++) {
- JSONObject pkgInfo = actual.getJSONObject(i);
- Pair<Integer, String> pkgKey =
- new Pair(pkgInfo.getInt("userId"), pkgInfo.getString("packageName"));
- assertTrue(expected.containsKey(pkgKey));
- assertThat(pkgInfo.getString("importance")).isEqualTo(expected.get(pkgKey));
- }
-
- // also make sure that (more likely to actually happen) if we don't provide an array of
- // app preferences (and do null instead), the same thing happens, so do the same checks
- JSONArray actualWithNullInput = (JSONArray) mHelper.dumpJson(
- new NotificationManagerService.DumpFilter(), null)
- .get("PackagePreferencess");
- assertThat(actualWithNullInput.length()).isEqualTo(expected.size());
- for (int i = 0; i < actualWithNullInput.length(); i++) {
- JSONObject pkgInfo = actualWithNullInput.getJSONObject(i);
- Pair<Integer, String> pkgKey =
- new Pair(pkgInfo.getInt("userId"), pkgInfo.getString("packageName"));
- assertTrue(expected.containsKey(pkgKey));
- assertThat(pkgInfo.getString("importance")).isEqualTo(expected.get(pkgKey));
- }
- }
-
- @Test
public void testDumpJson_postPermissionMigration() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// when getting a json dump, we want to verify that:
// - all notification importance info should come from the permission, even if the data
// isn't there yet but is present in package preferences
@@ -2844,11 +2758,8 @@
mHelper.createNotificationChannel(PKG_P, UID_P, channel1, true, false);
mHelper.createNotificationChannel(PKG_P, UID_P, channel2, false, false);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
// in the json array, all of the individual package preferences are simply elements in the
// values array. this set is to collect expected outputs for each of our packages.
@@ -2887,11 +2798,10 @@
public void testDumpJson_givenNullInput_postMigration() throws Exception {
// simple test just to make sure nothing dies if we pass in null input even post migration
// for some reason, even though in practice this should not be how one calls this method
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- // some packages exist, with some importance info that won't be looked at
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
+ // some packages exist
+ mHelper.canShowBadge(PKG_O, UID_O);
+ mHelper.canShowBadge(PKG_P, UID_P);
JSONArray actual = (JSONArray) mHelper.dumpJson(
new NotificationManagerService.DumpFilter(), null)
@@ -2908,44 +2818,16 @@
}
@Test
- public void testDumpBansJson_prePermissionMigration() throws Exception {
- // confirm that the package bans that are in json are only from package preferences, and
- // not from the passed-in permissions map
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
-
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
- appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
-
- // package preferences: only PKG_P is banned
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
-
- // make sure that's the only thing in the package ban output
- JSONArray actual = mHelper.dumpBansJson(
- new NotificationManagerService.DumpFilter(), appPermissions);
- assertThat(actual.length()).isEqualTo(1);
-
- JSONObject ban = actual.getJSONObject(0);
- assertThat(ban.getInt("userId")).isEqualTo(UserHandle.getUserId(UID_P));
- assertThat(ban.getString("packageName")).isEqualTo(PKG_P);
- }
-
- @Test
public void testDumpBansJson_postPermissionMigration() throws Exception {
// confirm that the package bans that are in the output include all packages that
// have their permission set to false, and not based on PackagePreferences importance
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
- // package preferences: PKG_O not banned based on local importance, and PKG_P is
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
+ mHelper.canShowBadge(PKG_O, UID_O);
// expected output
ArraySet<Pair<Integer, String>> expected = new ArraySet<>();
@@ -2967,10 +2849,6 @@
@Test
public void testDumpBansJson_givenNullInput() throws Exception {
// no one should do this, but...
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
-
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
JSONArray actual = mHelper.dumpBansJson(
new NotificationManagerService.DumpFilter(), null);
@@ -2978,59 +2856,8 @@
}
@Test
- public void testDumpString_prePermissionMigration() {
- // confirm that the string resulting from dumpImpl contains only info from package prefs
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
-
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
- appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
-
- // local package preferences: PKG_O is not banned even though the permissions would
- // indicate so
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
-
- // get dump output as a string so we can inspect the contents later
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- mHelper.dump(pw, "", new NotificationManagerService.DumpFilter(), appPermissions);
- pw.flush();
- String actual = sw.toString();
-
- // expected (substring) output for each preference
- ArrayList<String> expected = new ArrayList<>();
- expected.add(PKG_O + " (" + UID_O + ") importance=HIGH");
- expected.add(PKG_P + " (" + UID_P + ") importance=NONE");
-
- // make sure the things in app permissions do NOT show up
- ArrayList<String> notExpected = new ArrayList<>();
- notExpected.add("first (1) importance=DEFAULT");
- notExpected.add("third (3) importance=NONE");
- notExpected.add("userSet="); // no user-set information pre migration
-
- for (String exp : expected) {
- assertTrue(actual.contains(exp));
- }
-
- for (String notExp : notExpected) {
- assertFalse(actual.contains(notExp));
- }
-
- // also make sure it works the same if we pass in a null input
- StringWriter sw2 = new StringWriter();
- PrintWriter pw2 = new PrintWriter(sw2);
- mHelper.dump(pw2, "", new NotificationManagerService.DumpFilter(), null);
- pw.flush();
- String actualWithNullInput = sw2.toString();
- assertThat(actualWithNullInput).isEqualTo(actual);
- }
-
- @Test
public void testDumpString_postPermissionMigration() {
// confirm that the string resulting from dumpImpl contains only importances from permission
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
@@ -3038,8 +2865,8 @@
appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
+ mHelper.canShowBadge(PKG_O, UID_O);
+ mHelper.canShowBadge(PKG_P, UID_P);
// get dump output as a string so we can inspect the contents later
StringWriter sw = new StringWriter();
@@ -3072,11 +2899,10 @@
@Test
public void testDumpString_givenNullInput() {
// test that this doesn't choke on null input
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// local package preferences
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
+ mHelper.canShowBadge(PKG_O, UID_O);
+ mHelper.canShowBadge(PKG_P, UID_P);
// get dump output
StringWriter sw = new StringWriter();
@@ -3090,48 +2916,8 @@
}
@Test
- public void testDumpProto_prePermissionMigration() throws Exception {
- // test that dumping to proto gets the importances from the right place
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
-
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
- appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
-
- // local package preferences
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
-
- // expected output: only the local preferences
- // map format: (uid, package name) -> importance (int)
- ArrayMap<Pair<Integer, String>, Integer> expected = new ArrayMap<>();
- expected.put(new Pair(UID_O, PKG_O), IMPORTANCE_HIGH);
- expected.put(new Pair(UID_P, PKG_P), IMPORTANCE_NONE);
-
- // get the proto output and inspect its contents
- ProtoOutputStream proto = new ProtoOutputStream();
- mHelper.dump(proto, new NotificationManagerService.DumpFilter(), appPermissions);
-
- RankingHelperProto actual = RankingHelperProto.parseFrom(proto.getBytes());
- assertThat(actual.records.length).isEqualTo(expected.size());
- for (int i = 0; i < actual.records.length; i++) {
- RankingHelperProto.RecordProto record = actual.records[i];
- Pair<Integer, String> pkgKey = new Pair(record.uid, record.package_);
- assertTrue(expected.containsKey(pkgKey));
- assertThat(record.importance).isEqualTo(expected.get(pkgKey));
- }
-
- // also check that it's the same as passing in null input
- ProtoOutputStream proto2 = new ProtoOutputStream();
- mHelper.dump(proto2, new NotificationManagerService.DumpFilter(), null);
- assertThat(proto.getBytes()).isEqualTo(proto2.getBytes());
- }
-
- @Test
public void testDumpProto_postPermissionMigration() throws Exception {
// test that dumping to proto gets the importances from the right place
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// permissions -- these should take precedence
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
@@ -3140,8 +2926,8 @@
appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW);
+ mHelper.canShowBadge(PKG_O, UID_O);
+ mHelper.canShowBadge(PKG_P, UID_P);
// expected output: all the packages, but only the ones provided via appPermissions
// should have importance set (aka not PKG_P)
@@ -3429,14 +3215,6 @@
}
@Test
- public void testAppBlockedLogging() {
- mHelper.setEnabled(PKG_N_MR1, 1020, false);
- assertEquals(1, mLogger.getCalls().size());
- assertEquals(
- NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED,
- mLogger.get(0).event);
- }
- @Test
public void testXml_statusBarIcons_default() throws Exception {
String preQXml = "<ranking version=\"1\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -3517,7 +3295,7 @@
@Test
public void testIsDelegateAllowed_noDelegate() {
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+ mHelper.canShowBadge(PKG_O, UID_O);
assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "whatever", 0));
}
@@ -3555,7 +3333,7 @@
@Test
public void testDelegateXml_noDelegate() throws Exception {
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+ mHelper.canShowBadge(PKG_O, UID_O);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
@@ -3744,337 +3522,7 @@
}
@Test
- public void testLockChannelsForOEM_emptyList() {
- mHelper.lockChannelsForOEM(null);
- mHelper.lockChannelsForOEM(new String[0]);
- // no exception
- }
-
- @Test
- public void testLockChannelsForOEM_appWide() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
- // different uids, same package
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
- mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
- mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_onlyGivenPkg() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
- mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
- assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
- // different uids, same package
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
- mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
- mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet() {
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
- mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
- assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet() {
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
-
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
- // different uids, same package
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
- mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
- mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet_restoreData()
- throws Exception {
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- final String xml = "<ranking version=\"1\">\n"
- + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
- + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
- + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
- + "</package>"
- + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 + "\" >\n"
- + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
- + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
- + "</package>"
- + "</ranking>";
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false, UserHandle.USER_ALL);
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, "a", false)
- .isImportanceLockedByOEM());
- assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "b", false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet_restoreData_postMigration()
- throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- final String xml = "<ranking version=\"1\">\n"
- + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
- + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
- + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
- + "</package>"
- + "</ranking>";
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false, UserHandle.USER_ALL);
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, "a", false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet_restoreData()
- throws Exception {
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
-
- final String xml = "<ranking version=\"1\">\n"
- + "<package name=\"" + PKG_O + "\" uid=\"" + 3 + "\" >\n"
- + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
- + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
- + "</package>"
- + "<package name=\"" + PKG_O + "\" uid=\"" + 30 + "\" >\n"
- + "<channel id=\"c\" name=\"c\" importance=\"3\"/>"
- + "</package>"
- + "</ranking>";
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false, UserHandle.USER_ALL);
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "a", false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, "b", false)
- .isImportanceLockedByOEM());
- assertTrue(mHelper.getNotificationChannel(PKG_O, 30, "c", false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet_restoreData_postMigration()
- throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
-
- final String xml = "<ranking version=\"1\">\n"
- + "<package name=\"" + PKG_O + "\" uid=\"" + 3 + "\" >\n"
- + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
- + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
- + "</package>"
- + "<package name=\"" + PKG_O + "\" uid=\"" + 30 + "\" >\n"
- + "<channel id=\"c\" name=\"c\" importance=\"3\"/>"
- + "</package>"
- + "</ranking>";
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false, UserHandle.USER_ALL);
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "a", false)
- .isImportanceLockedByOEM());
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "b", false)
- .isImportanceLockedByOEM());
- assertFalse(mHelper.getNotificationChannel(PKG_O, 30, "c", false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific_clearData() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- mHelper.getImportance(PKG_O, UID_O);
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":" + a.getId()});
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
- assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.clearData(PKG_O, UID_O);
-
- // it's back!
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
- // and still locked
- assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.createNotificationChannel(PKG_O, 3, b, true, false);
- assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"});
-
- assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
- assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelSpecific_clearData_postMigration() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- mHelper.getImportance(PKG_O, UID_O);
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":" + a.getId()});
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
- assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.clearData(PKG_O, UID_O);
-
- // it's back!
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
- // and never locked
- assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelDoesNotExistYet_appWide_postMigration() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.createNotificationChannel(PKG_O, 3, b, true, false);
- assertFalse(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific_postMigration() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"});
-
- assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
- .isImportanceLockedByOEM());
-
- mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
- assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
- .isImportanceLockedByOEM());
- }
-
- @Test
- public void testUpdateNotificationChannel_oemLockedImportance() {
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
-
- mHelper.lockChannelsForOEM(new String[] {PKG_O});
-
- NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
- update.setAllowBubbles(false);
-
- mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
-
- assertEquals(IMPORTANCE_HIGH,
- mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
- assertEquals(false,
- mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());
-
- mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
-
- assertEquals(IMPORTANCE_HIGH,
- mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
- }
-
- @Test
public void testUpdateNotificationChannel_fixedPermission() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4093,7 +3541,6 @@
@Test
public void testUpdateNotificationChannel_fixedPermission_butUserPreviouslyBlockedIt() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_NONE);
@@ -4112,7 +3559,6 @@
@Test
public void testUpdateNotificationChannel_fixedPermission_butAppAllowsIt() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4132,7 +3578,6 @@
@Test
public void testUpdateNotificationChannel_notFixedPermission() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(false);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -5312,56 +4757,7 @@
}
@Test
- public void testPullPackagePreferencesStats_prePermissionMigration() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
-
- // build a collection of app permissions that should be passed in but ignored
- ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
- appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
-
- // package preferences: PKG_O not banned based on local importance, and PKG_P is
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
-
- // expected output. format: uid -> importance, as only uid (and not package name)
- // is in PackageNotificationPreferences
- ArrayMap<Integer, Integer> expected = new ArrayMap<>();
- expected.put(UID_O, IMPORTANCE_HIGH);
- expected.put(UID_P, IMPORTANCE_NONE);
-
- // unexpected output. these UIDs should not show up in the output at all
- ArraySet<Integer> unexpected = new ArraySet<>();
- unexpected.add(1);
- unexpected.add(3);
-
- ArrayList<StatsEvent> events = new ArrayList<>();
- mHelper.pullPackagePreferencesStats(events, appPermissions);
-
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) {
- int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER);
-
- // this shouldn't be any of the forbidden uids
- assertFalse(unexpected.contains(uid));
-
- // if it's one of the expected ids, then make sure the importance matches
- assertTrue(expected.containsKey(uid));
- assertThat(expected.get(uid)).isEqualTo(
- builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
-
- // pre-migration, the userSet field will always default to false
- boolean userSet = builder.getBoolean(
- PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
- assertFalse(userSet);
- }
- }
- }
-
- @Test
public void testPullPackagePreferencesStats_postPermissionMigration() {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// build a collection of app permissions that should be passed in but ignored
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
@@ -5369,9 +4765,9 @@
appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs
appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, true)); // in local prefs
- // package preferences: PKG_O not banned based on local importance, and PKG_P is
- mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
- mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE);
+ // local preferences
+ mHelper.canShowBadge(PKG_O, UID_O);
+ mHelper.canShowBadge(PKG_P, UID_P);
// expected output. format: uid -> importance, as only uid (and not package name)
// is in PackageNotificationPreferences
@@ -5433,27 +4829,4 @@
assertTrue((channelB.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
assertTrue((channelC.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
}
-
- @Test
- public void testDefaultChannelUpdatesApp_preMigrationToPermissions() throws Exception {
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
- UID_N_MR1,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(IMPORTANCE_NONE);
- mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);
-
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
- }
-
- @Test
- public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception {
- when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
- UID_N_MR1,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(IMPORTANCE_NONE);
- mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);
-
- assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
- }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 0bfd202..98c156e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -168,7 +168,8 @@
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
- mock(UsageStatsManagerInternal.class), mock (TelecomManager.class));
+ mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
+ mock(NotificationChannelLogger.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 533540e..a34896a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -611,10 +611,9 @@
activity.setRequestedOrientation(activityCurOrientation == ORIENTATION_LANDSCAPE
? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
- // Asserts fixed orientation request is ignored, and the orientation is not changed
- // (fill Task).
- assertEquals(activityCurOrientation, activity.getConfiguration().orientation);
- assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Asserts fixed orientation request is not ignored, and the orientation is changed.
+ assertNotEquals(activityCurOrientation, activity.getConfiguration().orientation);
+ assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
@@ -2854,6 +2853,11 @@
assertTrue(activity2.isResizeable());
activity1.reparent(taskFragment1, POSITION_TOP);
+ // Adds an Activity which doesn't have shared starting data, and verify if it blocks
+ // starting window removal.
+ final ActivityRecord activity3 = new ActivityBuilder(mAtm).build();
+ taskFragment2.addChild(activity3, POSITION_TOP);
+
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
assertEquals(activity1.mStartingData, startingWindow.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f59ec42..b943275 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -785,6 +785,34 @@
}
/**
+ * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will task the
+ * adjacent task of indicated launch target into account. So the existing task will be launched
+ * into closer target.
+ */
+ @Test
+ public void testAdjustLaunchTargetWithAdjacentTask() {
+ // Create adjacent tasks and put one activity under it
+ final Task parent = new TaskBuilder(mSupervisor).build();
+ final Task adjacentParent = new TaskBuilder(mSupervisor).build();
+ parent.setAdjacentTaskFragment(adjacentParent, true);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setParentTask(parent)
+ .setCreateTask(true).build();
+
+ // Launch the activity to its adjacent parent
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchRootTask(adjacentParent.mRemoteToken.toWindowContainerToken());
+ prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */)
+ .setReason("testAdjustLaunchTargetWithAdjacentTask")
+ .setIntent(activity.intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Verify the activity will be launched into the original parent
+ assertTrue(activity.isDescendantOf(parent));
+ }
+
+ /**
* This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will
* move the existing task to front if the current focused root task doesn't have running task.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 49cd343..873d9f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -306,6 +306,7 @@
if (focus) {
doReturn(window.getWindowInfo().token)
.when(mWindowManagerInternal).getFocusedWindowToken();
+ doReturn(window).when(mWm).getFocusedWindowLocked();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 76fb7ff..35d8129 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1876,6 +1876,28 @@
}
@Test
+ public void testResizableFixedOrientationAppInSplitScreen_letterboxForDifferentOrientation() {
+ setUpDisplaySizeWithApp(1000, 2800);
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Resizable landscape-only activity.
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, /* isUnresizable= */ false);
+
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, 1000, 1400);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+ // Resizable activity is not in size compat mode but in the letterbox for fixed orientation.
+ assertFitted();
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
public void testSupportsNonResizableInSplitScreen_fillTaskForSameOrientation() {
// Support non resizable in multi window
mAtm.mDevEnableNonResizableMultiWindow = true;
diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 177e819..2893f80 100644
--- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -23,6 +23,7 @@
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbRequest;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
import android.media.midi.MidiDeviceStatus;
@@ -44,6 +45,7 @@
import java.io.Closeable;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
@@ -73,10 +75,10 @@
// event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
- // Arbitrary number for timeout to not continue sending/receiving number from
- // an inactive device. This number tries to balances the number of cycles and
- // not being permanently stuck.
- private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100;
+ // Arbitrary number for timeout to not continue sending to
+ // an inactive device. This number tries to balances the number
+ // of cycles and not being permanently stuck.
+ private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 10;
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
@@ -330,48 +332,55 @@
new Thread("UsbDirectMidiDevice input thread " + portFinal) {
@Override
public void run() {
- byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
- Log.d(TAG, "input buffer size: " + inputBuffer.length);
+ final UsbRequest request = new UsbRequest();
try {
+ request.initialize(connectionFinal, endpointFinal);
+ byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
while (true) {
// Record time of event immediately after waking.
long timestamp = System.nanoTime();
- synchronized (mLock) {
- if (!mIsOpen) break;
+ if (!mIsOpen) break;
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(inputBuffer);
+ if (!request.queue(byteBuffer)) {
+ Log.w(TAG, "Cannot queue request");
+ break;
+ }
+ final UsbRequest response = connectionFinal.requestWait();
+ if (response != request) {
+ Log.w(TAG, "Unexpected response");
+ continue;
+ }
+ int bytesRead = byteBuffer.position();
- int nRead = connectionFinal.bulkTransfer(endpointFinal,
- inputBuffer, inputBuffer.length,
- BULK_TRANSFER_TIMEOUT_MILLISECONDS);
-
- if (nRead > 0) {
- if (DEBUG) {
- logByteArray("Input before conversion ", inputBuffer,
- 0, nRead);
- }
- byte[] convertedArray;
- if (mIsUniversalMidiDevice) {
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- convertedArray = swapEndiannessPerWord(inputBuffer,
- nRead);
- } else {
- convertedArray =
- mUsbMidiPacketConverter.usbMidiToRawMidi(
- inputBuffer, nRead);
- }
-
- if (DEBUG) {
- logByteArray("Input after conversion ", convertedArray,
- 0, convertedArray.length);
- }
-
- outputReceivers[portFinal].send(convertedArray, 0,
- convertedArray.length, timestamp);
+ if (bytesRead > 0) {
+ if (DEBUG) {
+ logByteArray("Input before conversion ", inputBuffer,
+ 0, bytesRead);
}
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(inputBuffer,
+ bytesRead);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.usbMidiToRawMidi(
+ inputBuffer, bytesRead);
+ }
+
+ if (DEBUG) {
+ logByteArray("Input after conversion ", convertedArray,
+ 0, convertedArray.length);
+ }
+ outputReceivers[portFinal].send(convertedArray, 0,
+ convertedArray.length, timestamp);
}
}
} catch (IOException e) {
Log.d(TAG, "reader thread exiting");
+ } finally {
+ request.close();
}
Log.d(TAG, "input thread exit");
}
@@ -564,6 +573,10 @@
Log.e(TAG, "Usb Interface is null");
return false;
}
+ if (connection == null) {
+ Log.e(TAG, "UsbDeviceConnection is null");
+ return false;
+ }
if (!connection.claimInterface(usbInterface, true)) {
Log.e(TAG, "Can't claim interface");
return false;
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index e3485de..ec94f8a 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -46,6 +46,14 @@
* See {@link PhoneAccount}, {@link TelecomManager}.
*/
public final class PhoneAccountHandle implements Parcelable {
+ /**
+ * Expected component name of Telephony phone accounts; ONLY used to determine if we should log
+ * the phone account handle ID.
+ */
+ private static final ComponentName TELEPHONY_COMPONENT_NAME =
+ new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196)
private final ComponentName mComponentName;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -136,14 +144,23 @@
@Override
public String toString() {
- // Note: Log.pii called for mId as it can contain personally identifying phone account
- // information such as SIP account IDs.
- return new StringBuilder().append(mComponentName)
- .append(", ")
- .append(Log.pii(mId))
- .append(", ")
- .append(mUserHandle)
- .toString();
+ StringBuilder sb = new StringBuilder()
+ .append(mComponentName)
+ .append(", ");
+
+ if (TELEPHONY_COMPONENT_NAME.equals(mComponentName)) {
+ // Telephony phone account handles are now keyed by subscription id which is not
+ // sensitive.
+ sb.append(mId);
+ } else {
+ // Note: Log.pii called for mId as it can contain personally identifying phone account
+ // information such as SIP account IDs.
+ sb.append(Log.pii(mId));
+ }
+ sb.append(", ");
+ sb.append(mUserHandle);
+
+ return sb.toString();
}
@Override
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 78b0b84..4924a82 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -68,7 +68,6 @@
public final class SmsApplication {
static final String LOG_TAG = "SmsApplication";
public static final String PHONE_PACKAGE_NAME = "com.android.phone";
- public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services";
public static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
public static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
@@ -541,11 +540,13 @@
PackageManager packageManager = context.getPackageManager();
AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ final String bluetoothPackageName = context.getResources()
+ .getString(com.android.internal.R.string.config_systemBluetoothStack);
// Assign permission to special system apps
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME, true);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
- BLUETOOTH_PACKAGE_NAME, true);
+ bluetoothPackageName, false);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
MMS_SERVICE_PACKAGE_NAME, true);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
@@ -1128,8 +1129,11 @@
return false;
}
final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
+ final String bluetoothPackageName = context.getResources()
+ .getString(com.android.internal.R.string.config_systemBluetoothStack);
+
if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
- || BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
+ || bluetoothPackageName.equals(packageName)) {
return true;
}
return false;
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index f47cf33..e7d95e4 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
package android.telephony;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+
import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_ANOMALY_DETECTED;
import android.annotation.NonNull;
@@ -73,6 +75,7 @@
*
* This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is
* system protected. Invoking this method unless you are the system will result in an error.
+ * Carrier Id will be set as UNKNOWN_CARRIER_ID.
*
* @param eventId a fixed event ID that will be sent for each instance of the same event. This
* ID should be generated randomly.
@@ -81,6 +84,23 @@
* static and must not contain any sensitive information (especially PII).
*/
public static void reportAnomaly(@NonNull UUID eventId, String description) {
+ reportAnomaly(eventId, description, UNKNOWN_CARRIER_ID);
+ }
+
+ /**
+ * If enabled, build and send an intent to a Debug Service for logging.
+ *
+ * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is
+ * system protected. Invoking this method unless you are the system will result in an error.
+ *
+ * @param eventId a fixed event ID that will be sent for each instance of the same event. This
+ * ID should be generated randomly.
+ * @param description an optional description, that if included will be used as the subject for
+ * identification and discussion of this event. This description should ideally be
+ * static and must not contain any sensitive information (especially PII).
+ * @param carrierId the carrier of the id associated with this event.
+ */
+ public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
if (sContext == null) {
Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
return;
@@ -88,7 +108,7 @@
TelephonyStatsLog.write(
TELEPHONY_ANOMALY_DETECTED,
- 0, // TODO: carrier id needs to be populated
+ carrierId,
eventId.getLeastSignificantBits(),
eventId.getMostSignificantBits());
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e21301e..70fe6b1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4980,6 +4980,25 @@
KEY_PREFIX + "use_sip_uri_for_presence_subscribe_bool";
/**
+ * Flag indicating whether or not to use TEL URI when setting the entity uri field and
+ * contact element of each tuple.
+ *
+ * When {@code true}, the device sets the entity uri field and contact element to be
+ * TEL URI. This is done by first searching for the first TEL URI provided in
+ * p-associated-uri header. If there are no TEL URIs in the p-associated-uri header, we will
+ * convert the first SIP URI provided in the header to a TEL URI. If there are no URIs in
+ * the p-associated-uri header, we will then fall back to using the SIM card to generate the
+ * TEL URI.
+ * If {@code false}, the first URI provided in the p-associated-uri header is used,
+ * independent of the URI scheme. If there are no URIs available from p-associated-uri
+ * header, we will try to generate a SIP URI or TEL URI from the information provided by the
+ * SIM card, depending on the information available.
+ * @hide
+ */
+ public static final String KEY_USE_TEL_URI_FOR_PIDF_XML_BOOL =
+ KEY_PREFIX + "use_tel_uri_for_pidf_xml";
+
+ /**
* An integer key associated with the period of time in seconds the non-rcs capability
* information of each contact is cached on the device.
* <p>
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index fa6de1a..ac1f376 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1673,4 +1673,9 @@
return UNKNOWN;
}
}
+
+ /** @hide */
+ public static boolean isFailCauseExisting(@DataFailureCause int failCause) {
+ return sFailCauseMap.containsKey(failCause);
+ }
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d07d809..2337a5a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3426,10 +3426,21 @@
* Get subscriptionInfo list of subscriptions that are in the same group of given subId.
* See {@link #createSubscriptionGroup(List)} for more details.
*
- * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
- * permission or had carrier privilege permission on the subscription.
+ * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
+ * or carrier privilege permission on the subscription.
* {@link TelephonyManager#hasCarrierPrivileges()}
*
+ * <p>Starting with API level 33, the caller needs the additional permission
+ * {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER}
+ * to get the list of subscriptions associated with a group UUID.
+ * This method can be invoked if one of the following requirements is met:
+ * <ul>
+ * <li>If the app has carrier privilege permission.
+ * {@link TelephonyManager#hasCarrierPrivileges()}
+ * <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} and
+ * {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} permission.
+ * </ul>
+ *
* @throws IllegalStateException if Telephony service is in bad state.
* @throws SecurityException if the caller doesn't meet the requirements
* outlined above.
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 843827b..1862412 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -118,8 +118,10 @@
@Override
public void setCapabilityExchangeEventListener(
@Nullable ICapabilityExchangeEventListener listener) throws RemoteException {
- CapabilityExchangeEventListener listenerWrapper =
- new CapabilityExchangeAidlWrapper(listener);
+ // Set the listener wrapper to null if the listener passed in is null. This will notify
+ // the RcsFeature to trigger the destruction of active capability exchange interface.
+ CapabilityExchangeEventListener listenerWrapper = listener != null
+ ? new CapabilityExchangeAidlWrapper(listener) : null;
executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listenerWrapper),
"setCapabilityExchangeEventListener");
}
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index d4fa1dd..62e16a5 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -37,8 +37,8 @@
"general-tests",
"vts",
],
- data_device_bins: [
- "block_device_writer",
+ target_required: [
+ "block_device_writer_module",
],
data: [
":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index e5d009d..fdfa41f 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,7 +24,12 @@
}
cc_test {
- name: "block_device_writer",
+ // Depending on how the test runs, the executable may be uploaded to different location.
+ // Before the bug in the file pusher is fixed, workaround by making the name unique.
+ // See b/124718249#comment12.
+ name: "block_device_writer_module",
+ stem: "block_device_writer",
+
srcs: ["block_device_writer.cpp"],
cflags: [
"-D_FILE_OFFSET_BITS=64",
@@ -37,7 +42,22 @@
"libbase",
"libutils",
],
- compile_multilib: "first",
+ // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
+ // the uploader does not pick up the executable from correct output location. The following
+ // workaround allows the test to:
+ // * upload the 32-bit exectuable for both 32 and 64 bits devices to use
+ // * refer to the same executable name in Java
+ // * no need to force the Java test to be archiecture specific.
+ //
+ // See b/145573317 for details.
+ multilib: {
+ lib32: {
+ suffix: "",
+ },
+ lib64: {
+ suffix: "64", // not really used
+ },
+ },
auto_gen_config: false,
test_suites: [
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 730daf3..5c2c15b 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@
* <p>To use this class, please push block_device_writer binary to /data/local/tmp.
* 1. In Android.bp, add:
* <pre>
- * data_device_bins: ["block_device_writer"],
+ * target_required: ["block_device_writer_module"],
* </pre>
* 2. In AndroidText.xml, add:
* <pre>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
new file mode 100644
index 0000000..16c4c25
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+class ImeStateInitializeHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file
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 9643909..b897ca2 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
@@ -28,6 +28,7 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
@@ -71,17 +72,20 @@
class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ private val initializeApp = ImeStateInitializeHelper(instrumentation)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
setup {
eachRun {
+ initializeApp.launchViaIntent()
this.setRotation(testSpec.startRotation)
}
}
teardown {
eachRun {
+ initializeApp.exit()
testApp.exit()
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 2842232..43aa4b1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -52,6 +52,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ImeStateInitializeActivity"
+ android:theme="@style/no_starting_window"
+ android:windowSoftInputMode="stateAlwaysHidden"
+ android:label="ImeStateInitializeActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".SeamlessRotationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
android:theme="@style/CutoutShortEdges"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 589df38..1d21fd5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -42,4 +42,8 @@
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
+
+ <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowDisablePreview">true</item>
+ </style>
</resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index e080709..6cda482 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -37,6 +37,11 @@
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeActivity");
+ public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
+ public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+
public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
new file mode 100644
index 0000000..4be79c4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A nop {@link Activity} to make sure that the test starts from a deterministic state.
+ *
+ * <p>Currently this {@link Activity} makes sure the following things</p>
+ * <li>
+ * <ul>Hide the software keyboard with
+ * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul>
+ * <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}.
+ * </ul>
+ * </li>
+ */
+public class ImeStateInitializeActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ final View view = new View(this);
+ view.setBackgroundColor(Color.WHITE);
+ view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Make sure that navigation bar is rendered with black (if supported).
+ getWindow().setNavigationBarColor(Color.BLACK);
+
+ setContentView(view);
+ }
+}
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 7ee19fb..052ce3a 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@@ -214,7 +215,7 @@
ApplicationInfo bluetoothApplicationInfo = new ApplicationInfo();
bluetoothApplicationInfo.uid = FAKE_BT_UID;
bluetoothPackageInfo.applicationInfo = bluetoothApplicationInfo;
- when(mPackageManager.getPackageInfo(eq(SmsApplication.BLUETOOTH_PACKAGE_NAME), anyInt()))
+ when(mPackageManager.getPackageInfo(matches(".*android.bluetooth.services"), anyInt()))
.thenReturn(bluetoothPackageInfo);
PackageInfo telephonyProviderPackageInfo = new PackageInfo();
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index 3c6d54d..ae72247 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -29,7 +29,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
-import com.google.common.truth.Truth.assertThat
+import android.trust.test.lib.wait
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -74,9 +74,9 @@
uiDevice.sleep()
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- uiDevice.wakeUp()
lockStateTrackingRule.assertLocked()
}
@@ -98,9 +98,9 @@
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- uiDevice.wakeUp()
lockStateTrackingRule.assertUnlocked()
}
@@ -116,6 +116,7 @@
uiDevice.sleep()
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
Log.i(TAG, "Renewing trust and unlocking")
var result: GrantTrustResult? = null
@@ -124,10 +125,9 @@
Log.i(TAG, "Callback received; status=${it.status}")
result = it
}
- uiDevice.wakeUp()
lockStateTrackingRule.assertUnlocked()
- assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT)
+ wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
}
@Test
@@ -141,7 +141,6 @@
trustAgentRule.agent.revokeTrust()
await(500)
uiDevice.wakeUp()
- await(500)
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}