Merge "Provide mechanism for on demand loading/unloading of connected plugins" into udc-dev
diff --git a/Android.bp b/Android.bp
index bc6cda3..cff863b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -343,6 +343,7 @@
"hardware/interfaces/biometrics/fingerprint/aidl",
"hardware/interfaces/graphics/common/aidl",
"hardware/interfaces/keymaster/aidl",
+ "system/hardware/interfaces/media/aidl",
],
},
dxflags: [
@@ -632,6 +633,7 @@
"hardware/interfaces/biometrics/fingerprint/aidl",
"hardware/interfaces/graphics/common/aidl",
"hardware/interfaces/keymaster/aidl",
+ "system/hardware/interfaces/media/aidl",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 90b6603..a46ecce 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -422,27 +422,26 @@
"$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
}
-// Disable doc generation until Doclava is migrated to JDK 17 (b/240421555).
-// java_genrule {
-// name: "ds-docs-switched",
-// tools: [
-// "switcher4",
-// "soong_zip",
-// ],
-// srcs: [
-// ":ds-docs-java{.docs.zip}",
-// ":ds-docs-kt{.docs.zip}",
-// ],
-// out: ["ds-docs-switched.zip"],
-// dist: {
-// targets: ["docs"],
-// },
-// cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
-// "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
-// "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
-// "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
-// "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-// }
+java_genrule {
+ name: "ds-docs-switched",
+ tools: [
+ "switcher4",
+ "soong_zip",
+ ],
+ srcs: [
+ ":ds-docs-java{.docs.zip}",
+ ":ds-docs-kt{.docs.zip}",
+ ],
+ out: ["ds-docs-switched.zip"],
+ dist: {
+ targets: ["docs"],
+ },
+ cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+ "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+ "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+ "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
+ "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
droiddoc {
name: "ds-static-docs",
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 1d93eb3..0650ce3 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -1970,7 +1970,7 @@
} break;
case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
updatePreIdleFactor();
- maybeDoImmediateMaintenance();
+ maybeDoImmediateMaintenance("idle factor");
} break;
case MSG_REPORT_STATIONARY_STATUS: {
final DeviceIdleInternal.StationaryListener newListener =
@@ -2625,7 +2625,7 @@
final Bundle mostRecentDeliveryOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -3517,11 +3517,11 @@
// doze alarm to after the upcoming AlarmClock alarm.
scheduleAlarmLocked(
mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime()
- + mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
+ + mConstants.QUICK_DOZE_DELAY_TIMEOUT);
} else {
// Wait a small amount of time in case something (eg: background service from
// recently closed app) needs to finish running.
- scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
+ scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT);
}
} else if (mState == STATE_ACTIVE) {
moveToStateLocked(STATE_INACTIVE, "no activity");
@@ -3536,9 +3536,9 @@
// alarm to after the upcoming AlarmClock alarm.
scheduleAlarmLocked(
mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime()
- + delay, false);
+ + delay);
} else {
- scheduleAlarmLocked(delay, false);
+ scheduleAlarmLocked(delay);
}
}
}
@@ -3753,7 +3753,7 @@
if (shouldUseIdleTimeoutFactorLocked()) {
delay = (long) (mPreIdleFactor * delay);
}
- scheduleAlarmLocked(delay, false);
+ scheduleAlarmLocked(delay);
moveToStateLocked(STATE_IDLE_PENDING, reason);
break;
case STATE_IDLE_PENDING:
@@ -3779,7 +3779,7 @@
case STATE_SENSING:
cancelSensingTimeoutAlarmLocked();
moveToStateLocked(STATE_LOCATING, reason);
- scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
+ scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
LocationManager locationManager = mInjector.getLocationManager();
if (locationManager != null
&& locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -3819,7 +3819,7 @@
// Everything is in place to go into IDLE state.
case STATE_IDLE_MAINTENANCE:
moveToStateLocked(STATE_IDLE, reason);
- scheduleAlarmLocked(mNextIdleDelay, true);
+ scheduleAlarmLocked(mNextIdleDelay);
if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
" ms.");
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
@@ -3842,7 +3842,7 @@
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
moveToStateLocked(STATE_IDLE_MAINTENANCE, reason);
- scheduleAlarmLocked(mNextIdlePendingDelay, false);
+ scheduleAlarmLocked(mNextIdlePendingDelay);
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
"Next alarm in " + mNextIdlePendingDelay + " ms.");
mMaintenanceStartTime = SystemClock.elapsedRealtime();
@@ -4013,19 +4013,18 @@
if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
return;
}
- scheduleAlarmLocked(newDelay, false);
+ scheduleAlarmLocked(newDelay);
}
}
}
- private void maybeDoImmediateMaintenance() {
+ private void maybeDoImmediateMaintenance(String reason) {
synchronized (this) {
if (mState == STATE_IDLE) {
long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
- /* Let's trgger a immediate maintenance,
- * if it has been idle for a long time */
+ // Trigger an immediate maintenance window if it has been IDLE for long enough.
if (duration > mConstants.IDLE_TIMEOUT) {
- scheduleAlarmLocked(0, false);
+ stepIdleStateLocked(reason);
}
}
}
@@ -4045,7 +4044,7 @@
void setIdleStartTimeForTest(long idleStartTime) {
synchronized (this) {
mIdleStartTime = idleStartTime;
- maybeDoImmediateMaintenance();
+ maybeDoImmediateMaintenance("testing");
}
}
@@ -4224,8 +4223,9 @@
}
@GuardedBy("this")
- void scheduleAlarmLocked(long delay, boolean idleUntil) {
- if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
+ @VisibleForTesting
+ void scheduleAlarmLocked(long delay) {
+ if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + stateToString(mState) + ")");
if (mUseMotionSensor && mMotionSensor == null
&& mState != STATE_QUICK_DOZE_DELAY
@@ -4241,7 +4241,7 @@
return;
}
mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
- if (idleUntil) {
+ if (mState == STATE_IDLE) {
mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
} else if (mState == STATE_LOCATING) {
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 1151bb7..26c0eef 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -30,6 +30,9 @@
import static android.app.AlarmManager.INTERVAL_HOUR;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.content.PermissionChecker.checkPermissionForPreflight;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
import static android.os.PowerExemptionManager.REASON_DENIED;
@@ -87,11 +90,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserPackage;
-import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryStatsInternal;
@@ -182,7 +183,6 @@
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
@@ -269,7 +269,8 @@
/**
* A map from uid to the last op-mode we have seen for
- * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}
+ * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
+ * when the denylist changes.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -1948,7 +1949,7 @@
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mTimeTickOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
mTimeTickTrigger = new IAlarmListener.Stub() {
@Override
@@ -2097,20 +2098,31 @@
if (oldMode == newMode) {
return;
}
- final boolean allowedByDefault =
- isScheduleExactAlarmAllowedByDefault(packageName, uid);
+ final boolean deniedByDefault = isScheduleExactAlarmDeniedByDefault(
+ packageName, UserHandle.getUserId(uid));
final boolean hadPermission;
- if (oldMode != AppOpsManager.MODE_DEFAULT) {
- hadPermission = (oldMode == AppOpsManager.MODE_ALLOWED);
- } else {
- hadPermission = allowedByDefault;
- }
final boolean hasPermission;
- if (newMode != AppOpsManager.MODE_DEFAULT) {
- hasPermission = (newMode == AppOpsManager.MODE_ALLOWED);
+
+ if (deniedByDefault) {
+ final boolean permissionState = getContext().checkPermission(
+ Manifest.permission.SCHEDULE_EXACT_ALARM, PID_UNKNOWN,
+ uid) == PackageManager.PERMISSION_GRANTED;
+ hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
+ ? permissionState
+ : (oldMode == AppOpsManager.MODE_ALLOWED);
+ hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
+ ? permissionState
+ : (newMode == AppOpsManager.MODE_ALLOWED);
} else {
- hasPermission = allowedByDefault;
+ final boolean allowedByDefault =
+ !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
+ hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
+ ? allowedByDefault
+ : (oldMode == AppOpsManager.MODE_ALLOWED);
+ hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
+ ? allowedByDefault
+ : (newMode == AppOpsManager.MODE_ALLOWED);
}
if (hadPermission && !hasPermission) {
@@ -2754,41 +2766,13 @@
boolean hasUseExactAlarmInternal(String packageName, int uid) {
return isUseExactAlarmEnabled(packageName, UserHandle.getUserId(uid))
- && (PermissionChecker.checkPermissionForPreflight(getContext(),
- Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid,
- packageName) == PermissionChecker.PERMISSION_GRANTED);
- }
-
- /**
- * Returns whether SCHEDULE_EXACT_ALARM is allowed by default.
- */
- boolean isScheduleExactAlarmAllowedByDefault(String packageName, int uid) {
- if (isScheduleExactAlarmDeniedByDefault(packageName, UserHandle.getUserId(uid))) {
-
- // This is essentially like changing the protection level of the permission to
- // (privileged|signature|role|appop), but have to implement this logic to maintain
- // compatibility for older apps.
- if (mPackageManagerInternal.isPlatformSigned(packageName)
- || mPackageManagerInternal.isUidPrivileged(uid)) {
- return true;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- final List<String> wellbeingHolders = (mRoleManager != null)
- ? mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)
- : Collections.emptyList();
- return wellbeingHolders.contains(packageName);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- return !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
+ && (checkPermissionForPreflight(getContext(), Manifest.permission.USE_EXACT_ALARM,
+ PID_UNKNOWN, uid, packageName) == PERMISSION_GRANTED);
}
boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
final long start = mStatLogger.getTime();
- // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService.
// Not using #mLastOpScheduleExactAlarm as it may contain stale values.
// No locking needed as all internal containers being queried are immutable.
final boolean hasPermission;
@@ -2796,11 +2780,16 @@
hasPermission = false;
} else if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
hasPermission = false;
+ } else if (isScheduleExactAlarmDeniedByDefault(packageName, UserHandle.getUserId(uid))) {
+ hasPermission = (checkPermissionForPreflight(getContext(),
+ Manifest.permission.SCHEDULE_EXACT_ALARM, PID_UNKNOWN, uid, packageName)
+ == PERMISSION_GRANTED);
} else {
+ // Compatibility permission check for older apps.
final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
packageName);
if (mode == AppOpsManager.MODE_DEFAULT) {
- hasPermission = isScheduleExactAlarmAllowedByDefault(packageName, uid);
+ hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
} else {
hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
}
@@ -4685,10 +4674,6 @@
return service.new ClockReceiver();
}
- void registerContentObserver(ContentObserver contentObserver, Uri uri) {
- mContext.getContentResolver().registerContentObserver(uri, false, contentObserver);
- }
-
void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER,
AppSchedulingModuleThread.getExecutor(), listener);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 3cc67e7..056b6b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -429,6 +429,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
boolean apiQuotaScheduleUpdated = false;
boolean concurrencyUpdated = false;
+ boolean persistenceUpdated = false;
boolean runtimeUpdated = false;
for (int controller = 0; controller < mControllers.size(); controller++) {
final StateController sc = mControllers.get(controller);
@@ -478,19 +479,23 @@
case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
- case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
- case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
- case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
- case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
- case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_UI_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_UI_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
+ case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS:
if (!runtimeUpdated) {
mConstants.updateRuntimeConstantsLocked();
runtimeUpdated = true;
}
break;
+ case Constants.KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS:
case Constants.KEY_PERSIST_IN_SPLIT_FILES:
- mConstants.updatePersistingConstantsLocked();
- mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+ if (!persistenceUpdated) {
+ mConstants.updatePersistingConstantsLocked();
+ mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+ persistenceUpdated = true;
+ }
break;
default:
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
@@ -572,20 +577,20 @@
"runtime_free_quota_max_limit_ms";
private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms";
private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
- private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
- "runtime_min_user_initiated_guarantee_ms";
- private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
- "runtime_user_initiated_limit_ms";
- private static final String
- KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
- "runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
- private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
- "runtime_min_user_initiated_data_transfer_guarantee_ms";
- private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
- "runtime_user_initiated_data_transfer_limit_ms";
+ private static final String KEY_RUNTIME_MIN_UI_GUARANTEE_MS = "runtime_min_ui_guarantee_ms";
+ private static final String KEY_RUNTIME_UI_LIMIT_MS = "runtime_ui_limit_ms";
+ private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ "runtime_min_ui_data_transfer_guarantee_buffer_factor";
+ private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+ "runtime_min_ui_data_transfer_guarantee_ms";
+ private static final String KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
+ "runtime_ui_data_transfer_limit_ms";
private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
+ private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
+ "max_num_persisted_job_work_items";
+
private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -610,17 +615,18 @@
public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
@VisibleForTesting
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
- public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ public static final long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
- public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
+ public static final long DEFAULT_RUNTIME_UI_LIMIT_MS =
Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
- public static final float
- DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
- public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
- Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
- public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
- Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
+ public static final float DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ 1.35f;
+ public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+ Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS);
+ public static final long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
+ Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
+ static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
/**
* Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -731,33 +737,31 @@
/**
* The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
*/
- public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
- DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+ public long RUNTIME_MIN_UI_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS;
/**
* The maximum amount of time we will let a user-initiated job run for. This will only
* apply if there are no other limits that apply to the specific user-initiated job.
*/
- public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
+ public long RUNTIME_UI_LIMIT_MS = DEFAULT_RUNTIME_UI_LIMIT_MS;
/**
* A factor to apply to estimated transfer durations for user-initiated data transfer jobs
* so that we give some extra time for unexpected situations. This will be at least 1 and
* so can just be multiplied with the original value to get the final value.
*/
- public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
- DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
+ public float RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
/**
* The minimum amount of time we try to guarantee user-initiated data transfer jobs
* will run for.
*/
- public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
- DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+ public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
/** The maximum amount of time we will let a user-initiated data transfer job run for. */
- public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
- DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ public long RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
/**
* Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
@@ -766,6 +770,11 @@
public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES;
/**
+ * The maximum number of {@link JobWorkItem JobWorkItems} that can be persisted per job.
+ */
+ public int MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS;
+
+ /**
* If true, use TARE policy for job limiting. If false, use quotas.
*/
public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
@@ -827,6 +836,10 @@
private void updatePersistingConstantsLocked() {
PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES);
+ MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS,
+ DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS);
}
private void updatePrefetchConstantsLocked() {
@@ -862,11 +875,11 @@
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
- KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
- KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
- KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
- KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
- KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
+ KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ KEY_RUNTIME_MIN_UI_GUARANTEE_MS,
+ KEY_RUNTIME_UI_LIMIT_MS,
+ KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+ KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS);
// Make sure min runtime for regular jobs is at least 10 minutes.
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -880,37 +893,35 @@
properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
// Make sure min runtime is at least as long as regular jobs.
- RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+ RUNTIME_MIN_UI_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(
- KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
- DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
+ KEY_RUNTIME_MIN_UI_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS));
// Max limit should be at least the min guarantee AND the free quota.
- RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
- Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ RUNTIME_UI_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ Math.max(RUNTIME_MIN_UI_GUARANTEE_MS,
properties.getLong(
- KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
- DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
+ KEY_RUNTIME_UI_LIMIT_MS, DEFAULT_RUNTIME_UI_LIMIT_MS)));
// The buffer factor should be at least 1 (so we don't decrease the time).
- RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
+ RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
properties.getFloat(
- KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
- DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
+ KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
));
// Make sure min runtime is at least as long as other user-initiated jobs.
- RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
- RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = Math.max(
+ RUNTIME_MIN_UI_GUARANTEE_MS,
properties.getLong(
- KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
- DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
+ KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
// User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will
// be higher than other jobs.
// Max limit should be the min guarantee and the max of other user-initiated jobs.
- RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
- RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
- Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
+ RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.max(
+ RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+ Math.max(RUNTIME_UI_LIMIT_MS,
properties.getLong(
- KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
- DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
+ KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+ DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS)));
}
private boolean updateTareSettingsLocked(@EconomyManager.EnabledMode int enabledMode) {
@@ -958,18 +969,18 @@
pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
- pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
- RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
- pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
- RUNTIME_USER_INITIATED_LIMIT_MS).println();
- pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
- RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
- pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
- RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
- pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
- RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_UI_GUARANTEE_MS, RUNTIME_MIN_UI_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_UI_LIMIT_MS, RUNTIME_UI_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
+ pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+ RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+ RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println();
pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
+ pw.print(KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, MAX_NUM_PERSISTED_JOB_WORK_ITEMS)
+ .println();
pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();
@@ -1353,6 +1364,25 @@
// Fast path: we are adding work to an existing job, and the JobInfo is not
// changing. We can just directly enqueue this work in to the job.
if (toCancel.getJob().equals(job)) {
+ // On T and below, JobWorkItem count was unlimited but they could not be
+ // persisted. Now in U and above, we allow persisting them. In both cases,
+ // there is a danger of apps adding too many JobWorkItems and causing the
+ // system to OOM since we keep everything in memory. The persisting danger
+ // is greater because it could technically lead to a boot loop if the system
+ // keeps trying to load all the JobWorkItems that led to the initial OOM.
+ // Therefore, for now (partly for app compatibility), we tackle the latter
+ // and limit the number of JobWorkItems that can be persisted.
+ // Moving forward, we should look into two things:
+ // 1. Limiting the number of unpersisted JobWorkItems
+ // 2. Offloading some state to disk so we don't keep everything in memory
+ // TODO(273758274): improve JobScheduler's resilience and memory management
+ if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ && toCancel.isPersisted()) {
+ Slog.w(TAG, "Too many JWIs for uid " + uId);
+ throw new IllegalStateException("Apps may not persist more than "
+ + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ + " JobWorkItems per job");
+ }
toCancel.enqueueWorkLocked(work);
mJobs.touchJob(toCancel);
@@ -1397,6 +1427,26 @@
jobStatus.prepareLocked();
if (toCancel != null) {
+ // On T and below, JobWorkItem count was unlimited but they could not be
+ // persisted. Now in U and above, we allow persisting them. In both cases,
+ // there is a danger of apps adding too many JobWorkItems and causing the
+ // system to OOM since we keep everything in memory. The persisting danger
+ // is greater because it could technically lead to a boot loop if the system
+ // keeps trying to load all the JobWorkItems that led to the initial OOM.
+ // Therefore, for now (partly for app compatibility), we tackle the latter
+ // and limit the number of JobWorkItems that can be persisted.
+ // Moving forward, we should look into two things:
+ // 1. Limiting the number of unpersisted JobWorkItems
+ // 2. Offloading some state to disk so we don't keep everything in memory
+ // TODO(273758274): improve JobScheduler's resilience and memory management
+ if (work != null && toCancel.isPersisted()
+ && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
+ Slog.w(TAG, "Too many JWIs for uid " + uId);
+ throw new IllegalStateException("Apps may not persist more than "
+ + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ + " JobWorkItems per job");
+ }
+
// Implicitly replaces the existing job record with the new instance
cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
@@ -1438,7 +1488,9 @@
/* isDeviceIdle */ false,
/* hasConnectivityConstraintSatisfied */ false,
/* hasContentTriggerConstraintSatisfied */ false,
- 0);
+ 0,
+ jobStatus.getJob().isUserInitiated(),
+ /* isRunningAsUserInitiatedJob */ false);
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1857,7 +1909,9 @@
cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
- 0);
+ 0,
+ cancelled.getJob().isUserInitiated(),
+ /* isRunningAsUserInitiatedJob */ false);
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -3256,19 +3310,18 @@
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
- return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+ return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
}
// Try to give the job at least as much time as we think the transfer will take,
// but cap it at the maximum limit
final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
- * mConstants
- .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
- return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+ return Math.min(mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
Math.max(factoredTransferTimeMs,
- mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
));
}
- return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+ return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
} else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3287,10 +3340,10 @@
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName());
if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT
- return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ return mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
}
if (allowLongerJob) { // UI with LRJ permission
- return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+ return mConstants.RUNTIME_UI_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index e60ed4a..4c339ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -462,7 +462,9 @@
job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
- mExecutionStartTimeElapsed - job.enqueueTime);
+ mExecutionStartTimeElapsed - job.enqueueTime,
+ job.getJob().isUserInitiated(),
+ job.shouldTreatAsUserInitiatedJob());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
@@ -1361,7 +1363,9 @@
completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
- 0);
+ 0,
+ completedJob.getJob().isUserInitiated(),
+ completedJob.startedAsUserInitiatedJob);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 537a670..0cc7758 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -814,6 +814,13 @@
return null;
}
+ /** Returns the number of {@link JobWorkItem JobWorkItems} attached to this job. */
+ public int getWorkCount() {
+ final int pendingCount = pendingWork == null ? 0 : pendingWork.size();
+ final int executingCount = executingWork == null ? 0 : executingWork.size();
+ return pendingCount + executingCount;
+ }
+
public boolean hasWorkLocked() {
return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
index 1ff389d..dffed0f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.Context;
import android.content.PermissionChecker;
@@ -41,7 +42,8 @@
@Nullable
public final String installerPackageName;
- InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) {
+ InstalledPackageInfo(@NonNull Context context, @UserIdInt int userId,
+ @NonNull PackageInfo packageInfo) {
final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
packageName = packageInfo.packageName;
@@ -55,7 +57,8 @@
applicationInfo.uid, packageName);
InstallSourceInfo installSourceInfo = null;
try {
- installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName);
+ installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName,
+ userId);
} catch (RemoteException e) {
// Shouldn't happen.
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index caf72e8..ffb2c03 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -625,7 +625,8 @@
mPackageToUidCache.add(userId, pkgName, uid);
}
synchronized (mLock) {
- final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo);
+ final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), userId,
+ packageInfo);
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
mUidToPackageCache.add(uid, pkgName);
@@ -683,7 +684,7 @@
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
final InstalledPackageInfo ipo =
- new InstalledPackageInfo(getContext(), pkgs.get(i));
+ new InstalledPackageInfo(getContext(), userId, pkgs.get(i));
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
}
@@ -963,7 +964,7 @@
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
final InstalledPackageInfo ipo =
- new InstalledPackageInfo(getContext(), pkgs.get(i));
+ new InstalledPackageInfo(getContext(), userId, pkgs.get(i));
final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
maybeUpdateInstallerStatusLocked(oldIpo, ipo);
}
diff --git a/api/api.go b/api/api.go
index 25d9728..9876abb 100644
--- a/api/api.go
+++ b/api/api.go
@@ -418,7 +418,6 @@
// combined_apis bp2build converter
func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
basePrefix := "non-updatable"
- scopeNames := []string{"public", "system", "module-lib", "system-server"}
scopeToSuffix := map[string]string{
"public": "-current.txt",
"system": "-system-current.txt",
@@ -426,8 +425,7 @@
"system-server": "-system-server-current.txt",
}
- for _, scopeName := range scopeNames{
- suffix := scopeToSuffix[scopeName]
+ for scopeName, suffix := range scopeToSuffix{
name := a.Name() + suffix
var scope bazel.StringAttribute
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 95070bd..09ec220 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -14799,7 +14799,6 @@
java.util.ImmutableCollections$Set12
java.util.ImmutableCollections$SetN
java.util.ImmutableCollections$SubList
-java.util.ImmutableCollections
java.util.InputMismatchException
java.util.Iterator
java.util.JumboEnumSet$EnumSetIterator
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index 502d8c6..a413bbd 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -9,4 +9,5 @@
android.net.rtp.AudioStream
android.net.rtp.RtpStream
java.util.concurrent.ThreadLocalRandom
+java.util.ImmutableCollections
com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/api/current.txt b/core/api/current.txt
index 7b97a65..79123c7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -224,6 +224,7 @@
field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+ field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
@@ -8048,6 +8049,7 @@
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
+ field public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED = "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED";
field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE";
@@ -9678,6 +9680,7 @@
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNext();
method @Nullable public String getPackageName();
+ method public int getPid();
method public int getUid();
method public boolean isTrusted(@NonNull android.content.Context);
method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9692,6 +9695,7 @@
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
+ method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
public abstract class BroadcastReceiver {
@@ -11401,7 +11405,7 @@
public class RestrictionsManager {
method public static android.os.Bundle convertRestrictionsToBundle(java.util.List<android.content.RestrictionEntry>);
method public android.content.Intent createLocalApprovalIntent();
- method @Deprecated public android.os.Bundle getApplicationRestrictions();
+ method public android.os.Bundle getApplicationRestrictions();
method @NonNull @WorkerThread public java.util.List<android.os.Bundle> getApplicationRestrictionsPerAdmin();
method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(String);
method public boolean hasRestrictionsProvider();
@@ -12812,6 +12816,7 @@
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
+ field public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT = "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
@@ -27342,7 +27347,9 @@
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data";
field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
+ field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype";
field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2
field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8
field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1
@@ -27472,7 +27479,7 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
- method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
+ method public void onTvMessage(int, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -33609,6 +33616,7 @@
method @Deprecated public static final boolean supportsProcesses();
field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
+ field public static final int INVALID_PID = -1; // 0xffffffff
field public static final int INVALID_UID = -1; // 0xffffffff
field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
field public static final int PHONE_UID = 1001; // 0x3e9
@@ -33860,7 +33868,7 @@
public class UserManager {
method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle);
- method @Deprecated @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
+ method @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
method public long getSerialNumberForUser(android.os.UserHandle);
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
method public long getUserCreationTime(android.os.UserHandle);
@@ -40587,7 +40595,7 @@
method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
- method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
+ method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
}
public class BeginGetCredentialOption implements android.os.Parcelable {
@@ -40636,7 +40644,7 @@
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setAuthenticationActions(@NonNull java.util.List<android.service.credentials.Action>);
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
- method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
+ method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
}
public final class CallingAppInfo implements android.os.Parcelable {
@@ -41554,7 +41562,6 @@
public abstract class RecognitionService extends android.app.Service {
ctor public RecognitionService();
- method public void clearModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
method public int getMaxConcurrentSessionsCount();
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract void onCancel(android.speech.RecognitionService.Callback);
@@ -41564,7 +41571,7 @@
method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
method public void onTriggerModelDownload(@NonNull android.content.Intent);
method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
- method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
+ method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
field public static final String SERVICE_META_DATA = "android.speech";
}
@@ -41689,18 +41696,17 @@
public class SpeechRecognizer {
method @MainThread public void cancel();
method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.RecognitionSupportCallback);
- method public void clearModelDownloadListener(@NonNull android.content.Intent);
method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
method public void destroy();
method public static boolean isOnDeviceRecognitionAvailable(@NonNull android.content.Context);
method public static boolean isRecognitionAvailable(@NonNull android.content.Context);
- method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
method @MainThread public void setRecognitionListener(android.speech.RecognitionListener);
method @MainThread public void startListening(android.content.Intent);
method @MainThread public void stopListening();
method public void triggerModelDownload(@NonNull android.content.Intent);
+ method public void triggerModelDownload(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
field public static final String CONFIDENCE_SCORES = "confidence_scores";
field public static final String DETECTED_LANGUAGE = "detected_language";
field public static final int ERROR_AUDIO = 3; // 0x3
@@ -53902,6 +53908,14 @@
method public void removeViewImmediate(android.view.View);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+ field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+ field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+ field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+ field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+ field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+ field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+ field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d56a937..0a893f0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -255,7 +255,6 @@
field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
- field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
@@ -1797,25 +1796,19 @@
field public final long bytesTransferred;
}
- public class BackupRestoreEventLogger {
- method public void logBackupMetaData(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
- method public void logItemsBackedUp(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
- method public void logItemsBackupFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
- method public void logItemsRestoreFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
- method public void logItemsRestored(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
- method public void logRestoreMetadata(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
- }
-
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreDataType {
- }
-
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreError {
+ public final class BackupRestoreEventLogger {
+ method public void logBackupMetadata(@NonNull String, @NonNull String);
+ method public void logItemsBackedUp(@NonNull String, int);
+ method public void logItemsBackupFailed(@NonNull String, int, @Nullable String);
+ method public void logItemsRestoreFailed(@NonNull String, int, @Nullable String);
+ method public void logItemsRestored(@NonNull String, int);
+ method public void logRestoreMetadata(@NonNull String, @NonNull String);
}
public static final class BackupRestoreEventLogger.DataTypeResult implements android.os.Parcelable {
ctor public BackupRestoreEventLogger.DataTypeResult(@NonNull String);
method public int describeContents();
- method @android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull public String getDataType();
+ method @NonNull public String getDataType();
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getErrors();
method public int getFailCount();
method @Nullable public byte[] getMetadataHash();
@@ -10141,16 +10134,18 @@
public final class SharedConnectivitySettingsState implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.Bundle getExtras();
+ method @Nullable public android.app.PendingIntent getInstantTetherSettingsPendingIntent();
method public boolean isInstantTetherEnabled();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState> CREATOR;
}
public static final class SharedConnectivitySettingsState.Builder {
- ctor public SharedConnectivitySettingsState.Builder();
+ ctor public SharedConnectivitySettingsState.Builder(@NonNull android.content.Context);
method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState build();
method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherEnabled(boolean);
+ method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherSettingsPendingIntent(@NonNull android.content.Intent);
}
}
@@ -13000,20 +12995,20 @@
package android.service.voice {
public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
- method @Nullable public android.content.Intent createEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @Nullable public android.content.Intent createReEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @Nullable public android.content.Intent createUnEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method @Nullable public android.content.Intent createEnrollIntent();
+ method @Nullable public android.content.Intent createReEnrollIntent();
+ method @Nullable public android.content.Intent createUnEnrollIntent();
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int);
method public int getSupportedAudioCapabilities();
- method public int getSupportedRecognitionModes() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method public int getSupportedRecognitionModes();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
+ method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
+ method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
@@ -13192,10 +13187,10 @@
public interface HotwordDetector {
method public default void destroy();
- method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
+ method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
+ method public boolean stopRecognition();
+ method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
}
public static interface HotwordDetector.Callback {
@@ -13209,9 +13204,6 @@
method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
}
- public static class HotwordDetector.IllegalDetectorStateException extends android.util.AndroidException {
- }
-
public final class HotwordRejectedResult implements android.os.Parcelable {
method public int describeContents();
method public int getConfidenceLevel();
@@ -13279,9 +13271,9 @@
public class VisualQueryDetector {
method public void destroy();
- method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
- method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
+ method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
+ method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
}
public static interface VisualQueryDetector.Callback {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 574ed6f..445b957 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2027,7 +2027,9 @@
method public static boolean is64BitAbi(String);
method public static boolean isDebuggable();
field @Nullable public static final String BRAND_FOR_ATTESTATION;
+ field @Nullable public static final String DEVICE_FOR_ATTESTATION;
field public static final boolean IS_EMULATOR;
+ field @Nullable public static final String MANUFACTURER_FOR_ATTESTATION;
field @Nullable public static final String MODEL_FOR_ATTESTATION;
field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
}
@@ -3986,6 +3988,7 @@
public static class WindowInfosListenerForTest.WindowInfo {
field @NonNull public final android.graphics.Rect bounds;
+ field public final boolean isTrustedOverlay;
field @NonNull public final String name;
field @NonNull public final android.os.IBinder windowToken;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3ada76..682fec8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -849,8 +849,10 @@
@UnsupportedAppUsage
Intent intent;
boolean rebind;
+ long bindSeq;
public String toString() {
- return "BindServiceData{token=" + token + " intent=" + intent + "}";
+ return "BindServiceData{token=" + token + " intent=" + intent
+ + " bindSeq=" + bindSeq + "}";
}
}
@@ -1107,12 +1109,13 @@
}
public final void scheduleBindService(IBinder token, Intent intent,
- boolean rebind, int processState) {
+ boolean rebind, int processState, long bindSeq) {
updateProcessState(processState, false);
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
s.rebind = rebind;
+ s.bindSeq = bindSeq;
if (DEBUG_SERVICE)
Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
@@ -1124,6 +1127,7 @@
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
+ s.bindSeq = -1;
sendMessage(H.UNBIND_SERVICE, s);
}
@@ -3557,8 +3561,13 @@
if (mLastProcessState == processState) {
return;
}
+ // Do not issue a transitional GC if we are transitioning between 2 cached states.
+ // Only update if the state flips between cached and uncached or vice versa
+ if (ActivityManager.isProcStateCached(mLastProcessState)
+ != ActivityManager.isProcStateCached(processState)) {
+ updateVmProcessState(processState);
+ }
mLastProcessState = processState;
- updateVmProcessState(processState);
if (localLOGV) {
Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
+ (fromIpc ? " (from ipc" : ""));
@@ -3567,12 +3576,16 @@
}
/** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
+ // Currently ART VM only uses state updates for Transitional GC, and thus
+ // this function initiates a Transitional GC for transitions into Cached apps states.
private void updateVmProcessState(int processState) {
- // TODO: Tune this since things like gmail sync are important background but not jank
- // perceptible.
- final int state = processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
- ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
- : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
+ // Only a transition into Cached state should result in a Transitional GC request
+ // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+ // Note that there are 4 possible cached states currently, all of which are
+ // JANK_IMPERCEPTIBLE from GC point of view.
+ final int state = ActivityManager.isProcStateCached(processState)
+ ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+ : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
VMRuntime.getRuntime().updateProcessState(state);
}
@@ -5827,26 +5840,6 @@
r.activity.mChangingConfigurations = true;
- // If we are preserving the main window across relaunches we would also like to preserve
- // the children. However the client side view system does not support preserving
- // the child views so we notify the window manager to expect these windows to
- // be replaced and defer requests to destroy or hide them. This way we can achieve
- // visual continuity. It's important that we do this here prior to pause and destroy
- // as that is when we may hide or remove the child views.
- //
- // There is another scenario, if we have decided locally to relaunch the app from a
- // call to recreate, then none of the windows will be prepared for replacement or
- // preserved by the server, so we want to notify it that we are preparing to replace
- // everything
- try {
- if (r.mPreserveWindow) {
- WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
- r.token, true /* childrenOnly */);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}
@@ -6644,12 +6637,13 @@
// Setup a location to store generated/compiled graphics code.
final Context deviceContext = context.createDeviceProtectedStorageContext();
final File codeCacheDir = deviceContext.getCodeCacheDir();
- if (codeCacheDir != null) {
+ final File deviceCacheDir = deviceContext.getCacheDir();
+ if (codeCacheDir != null && deviceCacheDir != null) {
try {
int uid = Process.myUid();
String[] packages = getPackageManager().getPackagesForUid(uid);
if (packages != null) {
- HardwareRenderer.setupDiskCache(codeCacheDir);
+ HardwareRenderer.setupDiskCache(deviceCacheDir);
RenderScriptCacheDir.setupDiskCache(codeCacheDir);
}
} catch (RemoteException e) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 181bd35..265b564 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8408,9 +8408,9 @@
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -8495,8 +8495,9 @@
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
- .getToken())), message,/*skipProxyOperation*/ false);
+ Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8906,9 +8907,9 @@
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -8954,7 +8955,7 @@
@Nullable String message) {
return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag,
+ Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
mContext.getAttributionSource().getToken())), message,
/*skipProxyOperation*/ false);
}
@@ -9103,8 +9104,8 @@
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
IBinder token = mContext.getAttributionSource().getToken();
finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- token)), /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+ proxiedAttributionTag, token)), /*skipProxyOperation*/ false);
}
/**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6301ad7..999075d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2561,7 +2561,7 @@
public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException {
final InstallSourceInfo installSourceInfo;
try {
- installSourceInfo = mPM.getInstallSourceInfo(packageName);
+ installSourceInfo = mPM.getInstallSourceInfo(packageName, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 762fb04..0cd42a3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3461,7 +3461,9 @@
@Nullable AttributionSource nextAttributionSource,
@Nullable Set<String> renouncedPermissions) {
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
- mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
+ Process.myPid(), mOpPackageName, attributionTag,
+ (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
+ nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f77203..6b5f6b0 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -95,7 +95,7 @@
void processInBackground();
@UnsupportedAppUsage
void scheduleBindService(IBinder token,
- in Intent intent, boolean rebind, int processState);
+ in Intent intent, boolean rebind, int processState, long bindSeq);
@UnsupportedAppUsage
void scheduleUnbindService(IBinder token,
in Intent intent);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 0ef8e92..09450f5 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -155,6 +155,14 @@
"android.app.extra.REMOTE_LOCKSCREEN_VALIDATION_SESSION";
/**
+ * A boolean indicating that credential confirmation activity should be a task overlay.
+ * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER}.
+ * @hide
+ */
+ public static final String EXTRA_FORCE_TASK_OVERLAY =
+ "android.app.KeyguardManager.FORCE_TASK_OVERLAY";
+
+ /**
* Result code returned by the activity started by
* {@link #createConfirmFactoryResetCredentialIntent} or
* {@link #createConfirmDeviceCredentialForRemoteValidationIntent}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 440ee20..0e89f57 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4634,9 +4634,9 @@
* Set whether this is an "ongoing" notification.
*
* Ongoing notifications cannot be dismissed by the user on locked devices, or by
- * notification listeners, and some notifications (device management, media) cannot be
- * dismissed on unlocked devices, so your application or service must take
- * care of canceling them.
+ * notification listeners, and some notifications (call, device management, media) cannot
+ * be dismissed on unlocked devices, so your application or service must take care of
+ * canceling them.
*
* They are typically used to indicate a background task that the user is actively engaged
* with (e.g., playing music) or is pending in some way and therefore occupying the device
@@ -8657,13 +8657,13 @@
* where the platform doesn't support the MIME type, the original text provided in the
* constructor will be used.
* @param dataMimeType The MIME type of the content. See
- * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
- * types on Android and Android Wear.
+ * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
+ * supported image MIME types.
* @param dataUri The uri containing the content whose type is given by the MIME type.
* <p class="note">
+ * Notification Listeners including the System UI need permission to access the
+ * data the Uri points to. The recommended ways to do this are:
* <ol>
- * <li>Notification Listeners including the System UI need permission to access the
- * data the Uri points to. The recommended ways to do this are:</li>
* <li>Store the data in your own ContentProvider, making sure that other apps have
* the correct permission to access your provider. The preferred mechanism for
* providing access is to use per-URI permissions which are temporary and only
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e72b141..f7d2afb 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@
per-file Broadcast* = file:/BROADCASTS_OWNERS
per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
+# KeyguardManager
+per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+
# LocaleManager
per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS
@@ -94,7 +97,5 @@
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
-# TODO(b/174932174): determine the ownership of KeyguardManager.java
-
# Zygote
per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 39d77c4..5b95503 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -875,15 +875,6 @@
}
/**
- * Returns true if any visible windows belonging to apps with this window configuration should
- * be kept on screen when the app is killed due to something like the low memory killer.
- * @hide
- */
- public boolean keepVisibleDeadAppWindowOnScreen() {
- return mWindowingMode != WINDOWING_MODE_PINNED;
- }
-
- /**
* Returns true if the backdrop on the client side should match the frame of the window.
* Returns false, if the backdrop should be fullscreen.
* @hide
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0241417..6bbbfe1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -57,6 +57,7 @@
import android.Manifest.permission;
import android.accounts.Account;
+import android.annotation.BroadcastBehavior;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.IntDef;
@@ -3998,6 +3999,27 @@
public static final String EXTRA_RESOURCE_IDS =
"android.app.extra.RESOURCE_IDS";
+ /**
+ * Broadcast Action: Broadcast sent to indicate that the device financing state has changed.
+ *
+ * <p>This occurs when, for example, a financing kiosk app has been added or removed.
+ *
+ * <p>To query the current device financing state see {@link #isDeviceFinanced}.
+ *
+ * <p>This will be delivered to the following apps if they include a receiver for this action
+ * in their manifest:
+ * <ul>
+ * <li>Device owner admins.
+ * <li>Organization-owned profile owner admins
+ * <li>The supervision app
+ * <li>The device management role holder
+ * </ul>
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true, includeBackground = true)
+ public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED =
+ "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
+
/** Allow the user to choose whether to enable MTE on the device. */
public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0;
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index c4ff892..ea31ef3 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -47,7 +47,7 @@
* @hide
*/
@SystemApi
-public class BackupRestoreEventLogger {
+public final class BackupRestoreEventLogger {
private static final String TAG = "BackupRestoreEventLogger";
/**
@@ -61,6 +61,8 @@
/**
* Denotes that the annotated element identifies a data type as required by the logging methods
* of {@code BackupRestoreEventLogger}
+ *
+ * @hide
*/
@Retention(RetentionPolicy.SOURCE)
public @interface BackupRestoreDataType {}
@@ -68,6 +70,8 @@
/**
* Denotes that the annotated element identifies an error type as required by the logging
* methods of {@code BackupRestoreEventLogger}
+ *
+ * @hide
*/
@Retention(RetentionPolicy.SOURCE)
public @interface BackupRestoreError {}
@@ -144,7 +148,7 @@
* @param dataType the type of data being backed up.
* @param metaData the metadata associated with the data type.
*/
- public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+ public void logBackupMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
logMetaData(OperationType.BACKUP, dataType, metaData);
}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2b400c1f..cd45f4d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -101,22 +101,28 @@
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag) {
- this(uid, packageName, attributionTag, sDefaultToken);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken);
}
/** @hide */
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
- /*next*/ null);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, token,
+ /*renouncedPermissions*/ null, /*next*/ null);
}
/** @hide */
- public AttributionSource(int uid, @Nullable String packageName,
- @Nullable String attributionTag, @NonNull IBinder token,
- @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
}
/** @hide */
@@ -124,26 +130,33 @@
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, (renouncedPermissions != null)
- ? renouncedPermissions.toArray(new String[0]) : null, next);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
+ (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
}
/** @hide */
public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
- this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
- current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+ this(current.getUid(), current.getPid(), current.getPackageName(),
+ current.getAttributionTag(), current.getToken(),
+ current.mAttributionSourceState.renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+ @Nullable AttributionSource next) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.pid = pid;
mAttributionSourceState.token = token;
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
@@ -162,7 +175,17 @@
// Since we just unpacked this object as part of it transiting a Binder
// call, this is the perfect time to enforce that its UID and PID can be trusted
- enforceCallingUidAndPid();
+ enforceCallingUid();
+
+ // If this object is being constructed as part of a oneway Binder call, getCallingPid will
+ // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
+ // INVALID_PID (-1).
+ final int callingPid = Binder.getCallingPid();
+ if (callingPid == 0) {
+ mAttributionSourceState.pid = Process.INVALID_PID;
+ }
+
+ enforceCallingPid();
}
/** @hide */
@@ -172,23 +195,29 @@
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, next);
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
- return new AttributionSource(getUid(), packageName, getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, getNext());
+ return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, getNext());
}
/** @hide */
public AttributionSource withToken(@NonNull Binder token) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
token, mAttributionSourceState.renouncedPermissions, getNext());
}
/** @hide */
+ public AttributionSource withPid(int pid) {
+ return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+ }
+
+ /** @hide */
public @NonNull AttributionSourceState asState() {
return mAttributionSourceState;
}
@@ -228,6 +257,7 @@
}
try {
return new AttributionSource.Builder(uid)
+ .setPid(Process.myPid())
.setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
.build();
} catch (Exception ignored) {
@@ -265,18 +295,6 @@
}
/**
- * If you are handling an IPC and you don't trust the caller you need to validate whether the
- * attribution source is one for the calling app to prevent the caller to pass you a source from
- * another app without including themselves in the attribution chain.
- *
- * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
- */
- private void enforceCallingUidAndPid() {
- enforceCallingUid();
- enforceCallingPid();
- }
-
- /**
* If you are handling an IPC and you don't trust the caller you need to validate
* whether the attribution source is one for the calling app to prevent the caller
* to pass you a source from another app without including themselves in the
@@ -312,7 +330,10 @@
}
/**
- * Validate that the pid being claimed for the calling app is not spoofed
+ * Validate that the pid being claimed for the calling app is not spoofed.
+ *
+ * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
+ * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
*
* @throws SecurityException if the attribution source cannot be trusted to be from the caller.
* @hide
@@ -320,8 +341,12 @@
@TestApi
public void enforceCallingPid() {
if (!checkCallingPid()) {
- throw new SecurityException("Calling pid: " + Binder.getCallingPid()
- + " doesn't match source pid: " + mAttributionSourceState.pid);
+ if (Binder.getCallingPid() == 0) {
+ throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
+ } else {
+ throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+ + " doesn't match source pid: " + mAttributionSourceState.pid);
+ }
}
}
@@ -332,7 +357,8 @@
*/
private boolean checkCallingPid() {
final int callingPid = Binder.getCallingPid();
- if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
+ if (mAttributionSourceState.pid != Process.INVALID_PID
+ && callingPid != mAttributionSourceState.pid) {
return false;
}
return true;
@@ -449,6 +475,13 @@
}
/**
+ * The PID that is accessing the permission protected data.
+ */
+ public int getPid() {
+ return mAttributionSourceState.pid;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @Nullable String getPackageName() {
@@ -551,6 +584,7 @@
throw new IllegalArgumentException("current AttributionSource can not be null");
}
mAttributionSourceState.uid = current.getUid();
+ mAttributionSourceState.pid = current.getPid();
mAttributionSourceState.packageName = current.getPackageName();
mAttributionSourceState.attributionTag = current.getAttributionTag();
mAttributionSourceState.token = current.getToken();
@@ -559,11 +593,25 @@
}
/**
+ * The PID of the process that is accessing the permission protected data.
+ *
+ * If not called, pid will default to {@link Process@INVALID_PID} (-1). This indicates that
+ * the PID data is missing. Supplying a PID is not required, but recommended when
+ * accessible.
+ */
+ public @NonNull Builder setPid(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAttributionSourceState.pid = value;
+ return this;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @NonNull Builder setPackageName(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2;
+ mBuilderFieldsSet |= 0x4;
mAttributionSourceState.packageName = value;
return this;
}
@@ -573,7 +621,7 @@
*/
public @NonNull Builder setAttributionTag(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
+ mBuilderFieldsSet |= 0x8;
mAttributionSourceState.attributionTag = value;
return this;
}
@@ -606,7 +654,7 @@
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8;
+ mBuilderFieldsSet |= 0x10;
mAttributionSourceState.renouncedPermissions = (value != null)
? value.toArray(new String[0]) : null;
return this;
@@ -617,7 +665,7 @@
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x20;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
{value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
@@ -629,15 +677,18 @@
mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x2) == 0) {
- mAttributionSourceState.packageName = null;
+ mAttributionSourceState.pid = Process.INVALID_PID;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
- mAttributionSourceState.attributionTag = null;
+ mAttributionSourceState.packageName = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.renouncedPermissions = null;
+ mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x10) == 0) {
+ mAttributionSourceState.renouncedPermissions = null;
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
mAttributionSourceState.next = null;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 85daf15..667ec7e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5920,9 +5920,8 @@
/**
* Optional argument to be used with {@link #ACTION_CHOOSER}.
- * A {@link android.app.PendingIntent} to be sent when the user wants to modify the content that
- * they're sharing. This can be used to allow the user to return to the source app to, for
- * example, select different media.
+ * A {@link ChooserAction} to allow the user to modify what is being shared in some way. This
+ * may be integrated into the content preview on sharesheets that have a preview UI.
*/
public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION =
"android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 8d3452e..0e3217d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
- packageName, attributionTag), message, startDataDelivery);
+ pid, packageName, attributionTag), message, startDataDelivery);
}
/**
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 8115292..44a84e4 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -427,11 +427,12 @@
* @return the application restrictions as a Bundle. Returns null if there
* are no restrictions.
*
- * @deprecated Use {@link #getApplicationRestrictionsPerAdmin} instead.
- * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
- * possible for there to be multiple managing agents on the device with the ability to set
- * restrictions. This API will only to return the restrictions set by device policy controllers
- * (DPCs)
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. a Device Policy Controller (DPC) and a Supervision admin.
+ * This API will only return the restrictions set by the DPCs. To retrieve restrictions
+ * set by all managing apps, use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
*
* @see DevicePolicyManager
*/
@@ -453,8 +454,8 @@
* stable between multiple calls.
*
* <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
- * it is possible for there to be multiple managing agents on the device with the ability to set
- * restrictions, e.g. an Enterprise DPC and a Supervision admin.
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
*
* <p>Each {@link Bundle} consists of key-value pairs, as defined by the application,
* where the types of values may be:
@@ -471,6 +472,7 @@
* package. Returns an empty {@link List} if there are no saved restrictions.
*
* @see UserManager#KEY_RESTRICTIONS_PENDING
+ * @see DevicePolicyManager
*/
@WorkerThread
@UserHandleAware
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 132b9af..410994d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -247,7 +247,7 @@
@UnsupportedAppUsage
String getInstallerPackageName(in String packageName);
- InstallSourceInfo getInstallSourceInfo(in String packageName);
+ InstallSourceInfo getInstallSourceInfo(in String packageName, int userId);
void resetApplicationPreferences(int userId);
diff --git a/core/java/android/content/pm/IncrementalStatesInfo.java b/core/java/android/content/pm/IncrementalStatesInfo.java
index 0393d34b..f15afdf 100644
--- a/core/java/android/content/pm/IncrementalStatesInfo.java
+++ b/core/java/android/content/pm/IncrementalStatesInfo.java
@@ -27,14 +27,18 @@
private boolean mIsLoading;
private float mProgress;
- public IncrementalStatesInfo(boolean isLoading, float progress) {
+ private long mLoadingCompletedTime;
+
+ public IncrementalStatesInfo(boolean isLoading, float progress, long loadingCompletedTime) {
mIsLoading = isLoading;
mProgress = progress;
+ mLoadingCompletedTime = loadingCompletedTime;
}
private IncrementalStatesInfo(Parcel source) {
mIsLoading = source.readBoolean();
mProgress = source.readFloat();
+ mLoadingCompletedTime = source.readLong();
}
public boolean isLoading() {
@@ -45,6 +49,10 @@
return mProgress;
}
+ public long getLoadingCompletedTime() {
+ return mLoadingCompletedTime;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -54,6 +62,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeBoolean(mIsLoading);
dest.writeFloat(mProgress);
+ dest.writeLong(mLoadingCompletedTime);
}
public static final @android.annotation.NonNull Creator<IncrementalStatesInfo> CREATOR =
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6486278..b9c671a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -204,6 +204,36 @@
"android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
/**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app can be opted-in or opted-out
+ * from the compatibility treatment that rotates camera output by 90 degrees on landscape
+ * sensors on devices known to have compatibility issues.
+ *
+ * <p>The treatment is disabled by default but device manufacturers can enable the treatment
+ * using their discretion to improve camera compatibility. With this property set to
+ * {@code false}, the rotation will not be applied. A value of {@code true}
+ * will ensure that rotation is applied, provided it is enabled for the device. In most cases,
+ * if rotation is the desired behavior this property need not be set. However, if your app
+ * experiences stretching or incorrect rotation on these devices, explicitly setting this to
+ * {@code true} may resolve that behavior. Apps should set this to {@code false} if there
+ * is confidence that the app handles
+ * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} correctly.
+ * See <a href="https://developer.android.com/training/camera2/camera-preview"> the
+ * documentation for best practice.</a>
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ */
+ public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
+ "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
+
+ /**
* A property value set within the manifest.
* <p>
* The value of a property will only have a single type, as defined by
@@ -8636,7 +8666,7 @@
* requesting its own install information and is not an instant app.
*
* @param packageName The name of the package to query
- * @throws NameNotFoundException if the given package name is not installed
+ * @throws NameNotFoundException if the given package name is not available to the caller.
*/
@NonNull
public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName)
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index bf34c1c..a23d7e4 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -42,7 +42,7 @@
private final String mType;
/**
- * The flattened JSON string that will be matched with requests.
+ * Flattened semicolon separated keys of JSON values to match with requests.
*/
@NonNull
private final String mFlattenedRequestString;
@@ -57,7 +57,8 @@
* Constructs a {@link CredentialDescription}.
*
* @param type the type of the credential returned.
- * @param flattenedRequestString flattened JSON string that will be matched with requests.
+ * @param flattenedRequestString flattened semicolon separated keys of JSON values
+ * to match with requests.
* @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
* account selector if a credential matches with this description.
* Each entry contains information to be displayed within an
@@ -151,4 +152,29 @@
public List<CredentialEntry> getCredentialEntries() {
return mCredentialEntries;
}
+
+ /**
+ * {@link CredentialDescription#mType} and
+ * {@link CredentialDescription#mFlattenedRequestString} are enough for hashing. Constructor
+ * enforces {@link CredentialEntry} to have the same type and
+ * {@link android.app.slice.Slice} contained by the entry can not be hashed.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mFlattenedRequestString);
+ }
+
+ /**
+ * {@link CredentialDescription#mType} and
+ * {@link CredentialDescription#mFlattenedRequestString} are enough for equality check.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CredentialDescription)) {
+ return false;
+ }
+ CredentialDescription other = (CredentialDescription) obj;
+ return mType.equals(other.mType)
+ && mFlattenedRequestString.equals(other.mFlattenedRequestString);
+ }
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 0806f1d..493a4ff 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -167,6 +167,48 @@
}
/**
+ * Gets a {@link GetPendingCredentialResponse} that can launch the credential retrieval UI flow
+ * to request a user credential for your app.
+ *
+ * @param request the request specifying type(s) of credentials to get from the user
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @hide
+ */
+ public void getPendingCredential(
+ @NonNull GetCredentialRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<
+ GetPendingCredentialResponse, GetCredentialException> callback) {
+ requireNonNull(request, "request must not be null");
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "getPendingCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote =
+ mService.executeGetPendingCredential(
+ request,
+ new GetPendingCredentialTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
+ /**
* Launches the necessary flows to register an app credential for the user.
*
* <p>The execution can potentially launch UI flows to collect user consent to creating or
@@ -442,6 +484,32 @@
}
}
+ private static class GetPendingCredentialTransport extends IGetPendingCredentialCallback.Stub {
+ // TODO: listen for cancellation to release callback.
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<
+ GetPendingCredentialResponse, GetCredentialException> mCallback;
+
+ private GetPendingCredentialTransport(
+ Executor executor,
+ OutcomeReceiver<GetPendingCredentialResponse, GetCredentialException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse(GetPendingCredentialResponse response) {
+ mExecutor.execute(() -> mCallback.onResult(response));
+ }
+
+ @Override
+ public void onError(String errorType, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new GetCredentialException(errorType, message)));
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/core/java/android/credentials/GetPendingCredentialResponse.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
copy to core/java/android/credentials/GetPendingCredentialResponse.aidl
index ad783da..1cdd637 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/core/java/android/credentials/GetPendingCredentialResponse.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -11,14 +11,9 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
+package android.credentials;
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
- val promptReason: Int = 0,
- val errorMessage: CharSequence? = null,
- val expansionAmount: Float = 0f,
-)
+parcelable GetPendingCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java
new file mode 100644
index 0000000..9005d9d
--- /dev/null
+++ b/core/java/android/credentials/GetPendingCredentialResponse.java
@@ -0,0 +1,127 @@
+/*
+ * 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.credentials;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * A response object that prefetches user app credentials and provides metadata about them. It can
+ * then be used to issue the full credential retrieval flow via the
+ * {@link #show(Activity, CancellationSignal, Executor, OutcomeReceiver)} method to perform the
+ * necessary flows such as consent collection and officially retrieve a credential.
+ *
+ * @hide
+ */
+public final class GetPendingCredentialResponse implements Parcelable {
+ private final boolean mHasCredentialResults;
+ private final boolean mHasAuthenticationResults;
+ private final boolean mHasRemoteResults;
+
+ /** Returns true if the user has any candidate credentials, and false otherwise. */
+ public boolean hasCredentialResults() {
+ return mHasCredentialResults;
+ }
+
+ /**
+ * Returns true if the user has any candidate authentication actions (locked credential
+ * supplier), and false otherwise.
+ */
+ public boolean hasAuthenticationResults() {
+ return mHasAuthenticationResults;
+ }
+
+ /**
+ * Returns true if the user has any candidate remote credential results, and false otherwise.
+ */
+ public boolean hasRemoteResults() {
+ return mHasRemoteResults;
+ }
+
+ /**
+ * Launches the necessary flows such as consent collection and credential selection to
+ * officially retrieve a credential among the pending credential candidates.
+ *
+ * @param activity the activity used to launch any UI needed
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ */
+ public void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+ // TODO(b/273308895): implement
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mHasCredentialResults);
+ dest.writeBoolean(mHasAuthenticationResults);
+ dest.writeBoolean(mHasRemoteResults);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "GetCredentialResponse {" + "credential=" + mHasCredentialResults + "}";
+ }
+
+ /**
+ * Constructs a {@link GetPendingCredentialResponse}.
+ *
+ * @param hasCredentialResults whether the user has any candidate credentials
+ * @param hasAuthenticationResults whether the user has any candidate authentication actions
+ * @param hasRemoteResults whether the user has any candidate remote options
+ */
+ public GetPendingCredentialResponse(boolean hasCredentialResults,
+ boolean hasAuthenticationResults, boolean hasRemoteResults) {
+ mHasCredentialResults = hasCredentialResults;
+ mHasAuthenticationResults = hasAuthenticationResults;
+ mHasRemoteResults = hasRemoteResults;
+ }
+
+ private GetPendingCredentialResponse(@NonNull Parcel in) {
+ mHasCredentialResults = in.readBoolean();
+ mHasAuthenticationResults = in.readBoolean();
+ mHasRemoteResults = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<GetPendingCredentialResponse> CREATOR = new Creator<>() {
+ @Override
+ public GetPendingCredentialResponse[] newArray(int size) {
+ return new GetPendingCredentialResponse[size];
+ }
+
+ @Override
+ public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new GetPendingCredentialResponse(in);
+ }
+ };
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 8c2cb5a..af8e7b4 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -27,6 +27,7 @@
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
@@ -40,6 +41,8 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
+ @nullable ICancellationSignal executeGetPendingCredential(in GetCredentialRequest request, in IGetPendingCredentialCallback callback, String callingPackage);
+
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
diff --git a/core/java/android/credentials/IGetPendingCredentialCallback.aidl b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
new file mode 100644
index 0000000..4ab0f99
--- /dev/null
+++ b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.credentials;
+
+import android.app.PendingIntent;
+import android.credentials.GetPendingCredentialResponse;
+
+/**
+ * Listener for a executeGetPendingCredential request.
+ *
+ * @hide
+ */
+interface IGetPendingCredentialCallback {
+ oneway void onResponse(in GetPendingCredentialResponse response);
+ oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 3341800..5e523c0 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -513,6 +513,19 @@
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
+ * <p>
+ * <em>Important:</em> You should NOT modify an existing migration step from version X to X+1
+ * once a build has been released containing that migration step. If a migration step has an
+ * error and it runs on a device, the step will NOT re-run itself in the future if a fix is made
+ * to the migration step.</p>
+ * <p>For example, suppose a migration step renames a database column from {@code foo} to
+ * {@code bar} when the name should have been {@code baz}. If that migration step is released
+ * in a build and runs on a user's device, the column will be renamed to {@code bar}. If the
+ * developer subsequently edits this same migration step to change the name to {@code baz} as
+ * intended, the user devices which have already run this step will still have the name
+ * {@code bar}. Instead, a NEW migration step should be created to correct the error and rename
+ * {@code bar} to {@code baz}, ensuring the error is corrected on devices which have already run
+ * the migration step with the error.</p>
*
* @param db The database.
* @param oldVersion The old database version.
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index cf20459..79a551a 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -54,6 +54,7 @@
private int mApiLevel;
private boolean mIsNdk;
private int mLatencyMs;
+ private long mLogId;
private int mSessionType;
private int mInternalReconfigure;
private long mRequestCount;
@@ -70,6 +71,7 @@
mApiLevel = -1;
mIsNdk = false;
mLatencyMs = -1;
+ mLogId = 0;
mMaxPreviewFps = 0;
mSessionType = -1;
mInternalReconfigure = -1;
@@ -82,7 +84,7 @@
public CameraSessionStats(String cameraId, int facing, int newCameraState,
String clientName, int apiLevel, boolean isNdk, int creationDuration,
- float maxPreviewFps, int sessionType, int internalReconfigure) {
+ float maxPreviewFps, int sessionType, int internalReconfigure, long logId) {
mCameraId = cameraId;
mFacing = facing;
mNewCameraState = newCameraState;
@@ -90,6 +92,7 @@
mApiLevel = apiLevel;
mIsNdk = isNdk;
mLatencyMs = creationDuration;
+ mLogId = logId;
mMaxPreviewFps = maxPreviewFps;
mSessionType = sessionType;
mInternalReconfigure = internalReconfigure;
@@ -127,6 +130,7 @@
dest.writeInt(mApiLevel);
dest.writeBoolean(mIsNdk);
dest.writeInt(mLatencyMs);
+ dest.writeLong(mLogId);
dest.writeFloat(mMaxPreviewFps);
dest.writeInt(mSessionType);
dest.writeInt(mInternalReconfigure);
@@ -146,6 +150,7 @@
mApiLevel = in.readInt();
mIsNdk = in.readBoolean();
mLatencyMs = in.readInt();
+ mLogId = in.readLong();
mMaxPreviewFps = in.readFloat();
mSessionType = in.readInt();
mInternalReconfigure = in.readInt();
@@ -189,6 +194,10 @@
return mLatencyMs;
}
+ public long getLogId() {
+ return mLogId;
+ }
+
public float getMaxPreviewFps() {
return mMaxPreviewFps;
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b9b310f..c95d081 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4123,7 +4123,8 @@
* counterparts.
* This key will only be present for devices which advertise the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability.</p>
+ * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
* <p><b>Units</b>: Pixel coordinates on the image sensor</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
@@ -4148,7 +4149,8 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
* This key will only be present for devices which advertise the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability.</p>
+ * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
* <p><b>Units</b>: Pixels</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
@@ -4172,7 +4174,8 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
* This key will only be present for devices which advertise the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability.</p>
+ * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
* <p><b>Units</b>: Pixel coordinates on the image sensor</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
@@ -4192,14 +4195,29 @@
* to improve various aspects of imaging such as noise reduction, low light
* performance etc. These groups can be of various sizes such as 2X2 (quad bayer),
* 3X3 (nona-bayer). This key specifies the length and width of the pixels grouped under
- * the same color filter.</p>
- * <p>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW images
- * will have a regular bayer pattern.</p>
- * <p>This key will not be present for sensors which don't have the
- * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability.</p>
+ * the same color filter.
+ * In case the device has the
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability :</p>
+ * <ul>
+ * <li>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW
+ * images will have a regular bayer pattern.</li>
+ * </ul>
+ * <p>In case the device does not have the
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability :</p>
+ * <ul>
+ * <li>This key will be present if
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}, since RAW
+ * images may not necessarily have a regular bayer pattern when
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</li>
+ * </ul>
* <p><b>Units</b>: Pixels</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 6cd4a2e..e6b3069 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -118,13 +118,6 @@
public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L;
/**
- * Package-level opt in/out for the above.
- * @hide
- */
- public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
- "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
-
- /**
* System property for allowing the above
* @hide
*/
@@ -132,6 +125,23 @@
"camera.enable_landscape_to_portrait";
/**
+ * Enable physical camera availability callbacks when the logical camera is unavailable
+ *
+ * <p>Previously once a logical camera becomes unavailable, no {@link
+ * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
+ * the logical camera becomes available again. The results in the app opening the logical
+ * camera not able to receive physical camera availability change.</p>
+ *
+ * <p>With this change, the {@link #onPhysicalCameraAvailable} and {@link
+ * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
+ * </p>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA =
+ 244358506L;
+
+ /**
* @hide
*/
public CameraManager(Context context) {
@@ -1189,7 +1199,8 @@
PackageManager packageManager = context.getPackageManager();
try {
- return packageManager.getProperty(PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
+ return packageManager.getProperty(
+ PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
context.getOpPackageName()).getBoolean();
} catch (PackageManager.NameNotFoundException e) {
// No such property
@@ -1200,6 +1211,14 @@
}
/**
+ * @hide
+ */
+ public static boolean physicalCallbacksAreEnabledForUnavailableCamera() {
+ return CompatChanges.isChangeEnabled(
+ ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA);
+ }
+
+ /**
* A callback for camera devices becoming available or unavailable to open.
*
* <p>Cameras become available when they are no longer in use, or when a new
@@ -1276,9 +1295,10 @@
* to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after
* {@link #onCameraAvailable}.</p>
*
- * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
- * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
- * if app A opens the camera device:</p>
+ * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+ * < {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+ * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+ * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
*
* <ul>
*
@@ -1290,6 +1310,33 @@
*
* </ul>
*
+ * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+ * ≥ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+ *
+ * <ul>
+ *
+ * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+ * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+ * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+ * physical cameras' availability status. This makes it possible for an application opening
+ * the logical camera device to know which physical camera becomes unavailable or available
+ * to use.</li>
+ *
+ * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+ * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+ * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+ * for any unavailable physical cameras upon the logical camera becoming available.</li>
+ *
+ * </ul>
+ *
+ * <p>Given the pipeline nature of the camera capture through {@link
+ * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+ * requests images from a physical camera of a logical multi-camera and that physical camera
+ * becomes unavailable. The application should stop requesting directly from an unavailable
+ * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+ * ready to robustly handle frame drop errors for requests targeting physical cameras,
+ * since those errors may arrive before the unavailability callback.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param cameraId The unique identifier of the logical multi-camera.
@@ -1312,9 +1359,10 @@
* cameras of its parent logical multi-camera, when {@link #onCameraUnavailable} for
* the logical multi-camera is invoked.</p>
*
- * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
- * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
- * if app A opens the camera device:</p>
+ * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+ * < {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+ * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+ * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
*
* <ul>
*
@@ -1326,6 +1374,33 @@
*
* </ul>
*
+ * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+ * ≥ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+ *
+ * <ul>
+ *
+ * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+ * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+ * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+ * physical cameras' availability status. This makes it possible for an application opening
+ * the logical camera device to know which physical camera becomes unavailable or available
+ * to use.</li>
+ *
+ * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+ * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+ * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+ * for any unavailable physical cameras upon the logical camera becoming available.</li>
+ *
+ * </ul>
+ *
+ * <p>Given the pipeline nature of the camera capture through {@link
+ * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+ * requests images from a physical camera of a logical multi-camera and that physical camera
+ * becomes unavailable. The application should stop requesting directly from an unavailable
+ * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+ * ready to robustly handle frame drop errors for requests targeting physical cameras,
+ * since those errors may arrive before the unavailability callback.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param cameraId The unique identifier of the logical multi-camera.
@@ -2289,7 +2364,8 @@
postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
// Send the NOT_PRESENT state for unavailable physical cameras
- if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+ if ((isAvailable(status) || physicalCallbacksAreEnabledForUnavailableCamera())
+ && mUnavailablePhysicalDevices.containsKey(id)) {
ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
for (String unavailableId : unavailableIds) {
postSingleUpdate(callback, executor, id, unavailableId,
@@ -2422,7 +2498,8 @@
return;
}
- if (!isAvailable(mDeviceStatus.get(id))) {
+ if (!physicalCallbacksAreEnabledForUnavailableCamera()
+ && !isAvailable(mDeviceStatus.get(id))) {
Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ "status change callback(s)", id));
return;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 705afc5..ed2a198 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3645,17 +3645,13 @@
//
/**
- * <p>This is the default sensor pixel mode. This is the only sensor pixel mode
- * supported unless a camera device advertises
- * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.</p>
+ * <p>This is the default sensor pixel mode.</p>
* @see CaptureRequest#SENSOR_PIXEL_MODE
*/
public static final int SENSOR_PIXEL_MODE_DEFAULT = 0;
/**
- * <p>This sensor pixel mode is offered by devices with capability
- * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.
- * In this mode, sensors typically do not bin pixels, as a result can offer larger
+ * <p>In this mode, sensors typically do not bin pixels, as a result can offer larger
* image sizes.</p>
* @see CaptureRequest#SENSOR_PIXEL_MODE
*/
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 381c87d..929868b 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1430,7 +1430,9 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability,
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1660,7 +1662,10 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1882,7 +1887,10 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3169,7 +3177,9 @@
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+ * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3517,13 +3527,10 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
* When operating in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
- * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability would typically perform pixel binning in order to improve low light
+ * would typically perform pixel binning in order to improve low light
* performance, noise reduction etc. However, in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * mode (supported only
- * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+ * mode, sensors typically operate in unbinned mode allowing for a larger image size.
* The stream configurations supported in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
* mode are also different from those of
@@ -3537,7 +3544,32 @@
* <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
* must not be mixed in the same CaptureRequest. In other words, these outputs are
* exclusive to each other.
- * This key does not need to be set for reprocess requests.</p>
+ * This key does not need to be set for reprocess requests.
+ * This key will be be present on devices supporting the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability. It may also be present on devices which do not support the aforementioned
+ * capability. In that case:</p>
+ * <ul>
+ * <li>
+ * <p>The mandatory stream combinations listed in
+ * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+ * would not apply.</p>
+ * </li>
+ * <li>
+ * <p>The bayer pattern of {@code RAW} streams when
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+ * </li>
+ * <li>
+ * <p>The following keys will always be present:</p>
+ * <ul>
+ * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+ * </ul>
+ * </li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -3548,6 +3580,9 @@
*
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
* @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 635e79c..a429f30 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -849,7 +849,9 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability,
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1329,7 +1331,10 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1962,7 +1967,10 @@
* mode.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where
+ * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3831,7 +3839,9 @@
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
* <p>For camera devices with the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+ * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+ * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
* coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -4442,13 +4452,10 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
* When operating in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
- * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * capability would typically perform pixel binning in order to improve low light
+ * would typically perform pixel binning in order to improve low light
* performance, noise reduction etc. However, in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * mode (supported only
- * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
- * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+ * mode, sensors typically operate in unbinned mode allowing for a larger image size.
* The stream configurations supported in
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
* mode are also different from those of
@@ -4462,7 +4469,32 @@
* <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
* must not be mixed in the same CaptureRequest. In other words, these outputs are
* exclusive to each other.
- * This key does not need to be set for reprocess requests.</p>
+ * This key does not need to be set for reprocess requests.
+ * This key will be be present on devices supporting the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability. It may also be present on devices which do not support the aforementioned
+ * capability. In that case:</p>
+ * <ul>
+ * <li>
+ * <p>The mandatory stream combinations listed in
+ * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+ * would not apply.</p>
+ * </li>
+ * <li>
+ * <p>The bayer pattern of {@code RAW} streams when
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+ * </li>
+ * <li>
+ * <p>The following keys will always be present:</p>
+ * <ul>
+ * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+ * </ul>
+ * </li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -4473,6 +4505,9 @@
*
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
* @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
*/
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index db83e62..2fa8b87 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -101,15 +101,15 @@
// Lock to synchronize cross-thread access to device public interface
- final Object mInterfaceLock = new Object(); // access from this class and Session only!
+ final Object mInterfaceLock;
/**
* @hide
*/
@RequiresPermission(android.Manifest.permission.CAMERA)
public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
- @NonNull CameraDevice cameraDevice, @NonNull Context ctx,
- @NonNull ExtensionSessionConfiguration config, int sessionId)
+ @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
+ @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
throws CameraAccessException, RemoteException {
long clientId = CameraExtensionCharacteristics.registerClient(ctx);
if (clientId < 0) {
@@ -209,7 +209,8 @@
}
private CameraAdvancedExtensionSessionImpl(long extensionClientId,
- @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice,
+ @NonNull IAdvancedExtenderImpl extender,
+ @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
@Nullable Surface postviewSurface,
@NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor,
@@ -228,6 +229,7 @@
mInitialized = false;
mInitializeHandler = new InitializeSessionHandler();
mSessionId = sessionId;
+ mInterfaceLock = cameraDevice.mInterfaceLock;
}
/**
@@ -599,13 +601,14 @@
public void onConfigured(@NonNull CameraCaptureSession session) {
synchronized (mInterfaceLock) {
mCaptureSession = session;
- try {
- CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to initialize session! Extension service does"
- + " not respond!");
- notifyConfigurationFailure();
- }
+ }
+
+ try {
+ CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize session! Extension service does"
+ + " not respond!");
+ notifyConfigurationFailure();
}
}
}
@@ -613,46 +616,56 @@
private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
@Override
public void onSuccess() {
- boolean status = true;
- synchronized (mInterfaceLock) {
- try {
- if (mSessionProcessor != null) {
- mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
- mInitialized = true;
- } else {
- Log.v(TAG, "Failed to start capture session, session released before " +
- "extension start!");
- status = false;
- mCaptureSession.close();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ boolean status = true;
+ synchronized (mInterfaceLock) {
+ try {
+ if (mSessionProcessor != null) {
+ mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
+ mInitialized = true;
+ } else {
+ Log.v(TAG, "Failed to start capture session, session " +
+ " released before extension start!");
+ status = false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to start capture session,"
+ + " extension service does not respond!");
+ status = false;
+ mInitialized = false;
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to start capture session,"
- + " extension service does not respond!");
- status = false;
- mCaptureSession.close();
- }
- }
- if (status) {
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(
- () -> mCallbacks.onConfigured(CameraAdvancedExtensionSessionImpl.this));
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (status) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallbacks.onConfigured(
+ CameraAdvancedExtensionSessionImpl.this));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ onFailure();
+ }
}
- } else {
- notifyConfigurationFailure();
- }
+ });
}
@Override
public void onFailure() {
- mCaptureSession.close();
- Log.e(TAG, "Failed to initialize proxy service session!"
- + " This can happen when trying to configure multiple "
- + "concurrent extension sessions!");
- notifyConfigurationFailure();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCaptureSession.close();
+
+ Log.e(TAG, "Failed to initialize proxy service session!"
+ + " This can happen when trying to configure multiple "
+ + "concurrent extension sessions!");
+ notifyConfigurationFailure();
+ }
+ });
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c2b3656..9c878c7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -117,7 +117,7 @@
private boolean mInternalRepeatingRequestEnabled = true;
// Lock to synchronize cross-thread access to device public interface
- final Object mInterfaceLock = new Object(); // access from this class and Session only!
+ final Object mInterfaceLock;
private static int nativeGetSurfaceFormat(Surface surface) {
return SurfaceUtils.getSurfaceFormat(surface);
@@ -128,7 +128,7 @@
*/
@RequiresPermission(android.Manifest.permission.CAMERA)
public static CameraExtensionSessionImpl createCameraExtensionSession(
- @NonNull CameraDevice cameraDevice,
+ @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@NonNull Context ctx,
@NonNull ExtensionSessionConfiguration config,
int sessionId)
@@ -251,7 +251,7 @@
@NonNull IPreviewExtenderImpl previewExtender,
@NonNull List<Size> previewSizes,
long extensionClientId,
- @NonNull CameraDevice cameraDevice,
+ @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@Nullable Surface repeatingRequestSurface,
@Nullable Surface burstCaptureSurface,
@Nullable Surface postviewSurface,
@@ -279,6 +279,7 @@
mSupportedRequestKeys = requestKeys;
mSupportedResultKeys = resultKeys;
mCaptureResultsSupported = !resultKeys.isEmpty();
+ mInterfaceLock = cameraDevice.mInterfaceLock;
}
private void initializeRepeatingRequestPipeline() throws RemoteException {
@@ -969,46 +970,56 @@
private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
@Override
public void onSuccess() {
- boolean status = true;
- ArrayList<CaptureStageImpl> initialRequestList =
- compileInitialRequestList();
- if (!initialRequestList.isEmpty()) {
- try {
- setInitialCaptureRequest(initialRequestList,
- new InitialRequestHandler(
- mRepeatingRequestImageCallback));
- } catch (CameraAccessException e) {
- Log.e(TAG,
- "Failed to initialize the initial capture "
- + "request!");
- status = false;
- }
- } else {
- try {
- setRepeatingRequest(mPreviewExtender.getCaptureStage(),
- new PreviewRequestHandler(null, null, null,
- mRepeatingRequestImageCallback));
- } catch (CameraAccessException | RemoteException e) {
- Log.e(TAG,
- "Failed to initialize internal repeating "
- + "request!");
- status = false;
- }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ boolean status = true;
+ ArrayList<CaptureStageImpl> initialRequestList =
+ compileInitialRequestList();
+ if (!initialRequestList.isEmpty()) {
+ try {
+ setInitialCaptureRequest(initialRequestList,
+ new InitialRequestHandler(
+ mRepeatingRequestImageCallback));
+ } catch (CameraAccessException e) {
+ Log.e(TAG,
+ "Failed to initialize the initial capture "
+ + "request!");
+ status = false;
+ }
+ } else {
+ try {
+ setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+ new PreviewRequestHandler(null, null, null,
+ mRepeatingRequestImageCallback));
+ } catch (CameraAccessException | RemoteException e) {
+ Log.e(TAG,
+ "Failed to initialize internal repeating "
+ + "request!");
+ status = false;
+ }
- }
+ }
- if (!status) {
- notifyConfigurationFailure();
- }
+ if (!status) {
+ notifyConfigurationFailure();
+ }
+ }
+ });
}
@Override
public void onFailure() {
- mCaptureSession.close();
- Log.e(TAG, "Failed to initialize proxy service session!"
- + " This can happen when trying to configure multiple "
- + "concurrent extension sessions!");
- notifyConfigurationFailure();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCaptureSession.close();
+ Log.e(TAG, "Failed to initialize proxy service session!"
+ + " This can happen when trying to configure multiple "
+ + "concurrent extension sessions!");
+ notifyConfigurationFailure();
+ }
+ });
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 631df01..9743c1f 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1618,15 +1618,20 @@
}
private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() {
- if (!isUltraHighResolutionSensor()) {
- return null;
- }
StreamConfiguration[] configurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] minFrameDurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] stallDurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ // If the at least these keys haven't been advertised, there cannot be a meaningful max
+ // resolution StreamConfigurationMap
+ if (configurations == null ||
+ minFrameDurations == null ||
+ stallDurations == null) {
+ return null;
+ }
+
StreamConfiguration[] depthConfigurations = getBase(
CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] depthMinFrameDurations = getBase(
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 34c8336..7cd627d 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -181,13 +181,15 @@
* for a given camera id in order to retrieve the device capabilities.</p>
*
* @param elements
- * An array of elements describing the map. It contains two elements per entry which
- * describe the supported dynamic range profile value in the first element and in the
- * second element a bitmap of concurrently supported dynamic range profiles within the
- * same capture request. Bitmap values of 0 indicate that there are no constraints.
+ * An array of elements describing the map. It contains three elements per entry. The
+ * first element describes the supported dynamic range profile value. The
+ * second element contains a bitmap of concurrently supported dynamic range profiles
+ * within the same capture request. The third element contains a hint about
+ * extra latency associated with the corresponding dynamic range. Bitmap values of 0
+ * indicate that there are no constraints.
*
* @throws IllegalArgumentException
- * if the {@code elements} array length is invalid, not divisible by 2 or contains
+ * if the {@code elements} array length is invalid, not divisible by 3 or contains
* invalid element values
* @throws NullPointerException
* if {@code elements} is {@code null}
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index 8a40d00..aa55e54 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -56,7 +56,7 @@
private static final int MSG_SENSOR_ACCURACY_CHANGED = 1;
private static final int MSG_SENSOR_CHANGED = 2;
- private InputManager mInputManager;
+ private InputManagerGlobal mGlobal;
// sensor map from device id to sensor list
@GuardedBy("mInputSensorLock")
@@ -70,15 +70,15 @@
private final HandlerThread mSensorThread;
private final Handler mSensorHandler;
- public InputDeviceSensorManager(InputManager inputManager) {
- mInputManager = inputManager;
+ public InputDeviceSensorManager(InputManagerGlobal inputManagerGlobal) {
+ mGlobal = inputManagerGlobal;
mSensorThread = new HandlerThread("SensorThread");
mSensorThread.start();
mSensorHandler = new Handler(mSensorThread.getLooper());
// Register the input device listener
- mInputManager.registerInputDeviceListener(this, mSensorHandler);
+ mGlobal.registerInputDeviceListener(this, mSensorHandler);
// Initialize the sensor list
initializeSensors();
}
@@ -100,7 +100,7 @@
final InputDevice inputDevice = InputDevice.getDevice(deviceId);
if (inputDevice != null && inputDevice.hasSensor()) {
final InputSensorInfo[] sensorInfos =
- mInputManager.getSensorList(deviceId);
+ mGlobal.getSensorList(deviceId);
populateSensorsForInputDeviceLocked(deviceId, sensorInfos);
}
}
@@ -154,7 +154,7 @@
private void initializeSensors() {
synchronized (mInputSensorLock) {
mSensors.clear();
- int[] deviceIds = mInputManager.getInputDeviceIds();
+ int[] deviceIds = mGlobal.getInputDeviceIds();
for (int i = 0; i < deviceIds.length; i++) {
final int deviceId = deviceIds[i];
updateInputDeviceSensorInfoLocked(deviceId);
@@ -455,7 +455,7 @@
Slog.e(TAG, "The device doesn't have the sensor:" + sensor);
return false;
}
- if (!mInputManager.enableSensor(deviceId, sensor.getType(), delayUs,
+ if (!mGlobal.enableSensor(deviceId, sensor.getType(), delayUs,
maxBatchReportLatencyUs)) {
Slog.e(TAG, "Can't enable the sensor:" + sensor);
return false;
@@ -467,7 +467,7 @@
// Register the InputManagerService sensor listener if not yet.
if (mInputServiceSensorListener == null) {
mInputServiceSensorListener = new InputSensorEventListener();
- if (!mInputManager.registerSensorListener(mInputServiceSensorListener)) {
+ if (!mGlobal.registerSensorListener(mInputServiceSensorListener)) {
Slog.e(TAG, "Failed registering the sensor listener");
return false;
}
@@ -516,7 +516,7 @@
}
// If no delegation remains, unregister the listener to input service
if (mInputServiceSensorListener != null && mInputSensorEventListeners.size() == 0) {
- mInputManager.unregisterSensorListener(mInputServiceSensorListener);
+ mGlobal.unregisterSensorListener(mInputServiceSensorListener);
mInputServiceSensorListener = null;
}
// For each sensor type check if it is still in use by other listeners.
@@ -539,7 +539,7 @@
if (DEBUG) {
Slog.d(TAG, "device " + deviceId + " sensor " + sensorType + " disabled");
}
- mInputManager.disableSensor(deviceId, sensorType);
+ mGlobal.disableSensor(deviceId, sensorType);
}
}
}
@@ -553,7 +553,7 @@
}
for (Sensor sensor : mInputSensorEventListeners.get(idx).getSensors()) {
final int deviceId = sensor.getId();
- if (!mInputManager.flushSensor(deviceId, sensor.getType())) {
+ if (!mGlobal.flushSensor(deviceId, sensor.getType())) {
return false;
}
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 490589f..054ae21 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -44,8 +44,6 @@
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.InputEventInjectionSync;
-import android.os.Looper;
-import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -53,7 +51,6 @@
import android.os.Vibrator;
import android.os.VibratorManager;
import android.util.Log;
-import android.util.SparseArray;
import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -66,9 +63,7 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -88,10 +83,6 @@
// To enable these logs, run: 'adb shell setprop log.tag.InputManager DEBUG' (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int MSG_DEVICE_ADDED = 1;
- private static final int MSG_DEVICE_REMOVED = 2;
- private static final int MSG_DEVICE_CHANGED = 3;
-
private static InputManager sInstance;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -112,33 +103,6 @@
@Nullable
private Boolean mIsStylusPointerIconEnabled = null;
- // Guarded by mInputDevicesLock
- private final Object mInputDevicesLock = new Object();
- private SparseArray<InputDevice> mInputDevices;
- private InputDevicesChangedListener mInputDevicesChangedListener;
- private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
-
- // Guarded by mTabletModeLock
- private final Object mTabletModeLock = new Object();
- @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
- private TabletModeChangedListener mTabletModeChangedListener;
- private ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
-
- private final Object mBatteryListenersLock = new Object();
- // Maps a deviceId whose battery is currently being monitored to an entry containing the
- // registered listeners for that device.
- @GuardedBy("mBatteryListenersLock")
- private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
- @GuardedBy("mBatteryListenersLock")
- private IInputDeviceBatteryListener mInputDeviceBatteryListener;
-
- private final Object mKeyboardBacklightListenerLock = new Object();
- @GuardedBy("mKeyboardBacklightListenerLock")
- private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
- @GuardedBy("mKeyboardBacklightListenerLock")
- private IKeyboardBacklightListener mKeyboardBacklightListener;
-
- private InputDeviceSensorManager mInputDeviceSensorManager;
/**
* Broadcast Action: Query available keyboard layouts.
* <p>
@@ -403,27 +367,7 @@
*/
@Nullable
public InputDevice getInputDevice(int id) {
- synchronized (mInputDevicesLock) {
- populateInputDevicesLocked();
-
- int index = mInputDevices.indexOfKey(id);
- if (index < 0) {
- return null;
- }
-
- InputDevice inputDevice = mInputDevices.valueAt(index);
- if (inputDevice == null) {
- try {
- inputDevice = mIm.getInputDevice(id);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- if (inputDevice != null) {
- mInputDevices.setValueAt(index, inputDevice);
- }
- }
- return inputDevice;
- }
+ return mGlobal.getInputDevice(id);
}
/**
@@ -433,34 +377,7 @@
* @hide
*/
public InputDevice getInputDeviceByDescriptor(String descriptor) {
- if (descriptor == null) {
- throw new IllegalArgumentException("descriptor must not be null.");
- }
-
- synchronized (mInputDevicesLock) {
- populateInputDevicesLocked();
-
- int numDevices = mInputDevices.size();
- for (int i = 0; i < numDevices; i++) {
- InputDevice inputDevice = mInputDevices.valueAt(i);
- if (inputDevice == null) {
- int id = mInputDevices.keyAt(i);
- try {
- inputDevice = mIm.getInputDevice(id);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- if (inputDevice == null) {
- continue;
- }
- mInputDevices.setValueAt(i, inputDevice);
- }
- if (descriptor.equals(inputDevice.getDescriptor())) {
- return inputDevice;
- }
- }
- return null;
- }
+ return mGlobal.getInputDeviceByDescriptor(descriptor);
}
/**
@@ -468,16 +385,7 @@
* @return The input device ids.
*/
public int[] getInputDeviceIds() {
- synchronized (mInputDevicesLock) {
- populateInputDevicesLocked();
-
- final int count = mInputDevices.size();
- final int[] ids = new int[count];
- for (int i = 0; i < count; i++) {
- ids[i] = mInputDevices.keyAt(i);
- }
- return ids;
- }
+ return mGlobal.getInputDeviceIds();
}
/**
@@ -547,17 +455,7 @@
* @see #unregisterInputDeviceListener
*/
public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- synchronized (mInputDevicesLock) {
- populateInputDevicesLocked();
- int index = findInputDeviceListenerLocked(listener);
- if (index < 0) {
- mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
- }
- }
+ mGlobal.registerInputDeviceListener(listener, handler);
}
/**
@@ -568,28 +466,7 @@
* @see #registerInputDeviceListener
*/
public void unregisterInputDeviceListener(InputDeviceListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- synchronized (mInputDevicesLock) {
- int index = findInputDeviceListenerLocked(listener);
- if (index >= 0) {
- InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
- d.removeCallbacksAndMessages(null);
- mInputDeviceListeners.remove(index);
- }
- }
- }
-
- private int findInputDeviceListenerLocked(InputDeviceListener listener) {
- final int numListeners = mInputDeviceListeners.size();
- for (int i = 0; i < numListeners; i++) {
- if (mInputDeviceListeners.get(i).mListener == listener) {
- return i;
- }
- }
- return -1;
+ mGlobal.unregisterInputDeviceListener(listener);
}
/**
@@ -618,20 +495,7 @@
*/
public void registerOnTabletModeChangedListener(
OnTabletModeChangedListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
- synchronized (mTabletModeLock) {
- if (mOnTabletModeChangedListeners == null) {
- initializeTabletModeListenerLocked();
- }
- int idx = findOnTabletModeChangedListenerLocked(listener);
- if (idx < 0) {
- OnTabletModeChangedListenerDelegate d =
- new OnTabletModeChangedListenerDelegate(listener, handler);
- mOnTabletModeChangedListeners.add(d);
- }
- }
+ mGlobal.registerOnTabletModeChangedListener(listener, handler);
}
/**
@@ -641,37 +505,7 @@
* @hide
*/
public void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
- synchronized (mTabletModeLock) {
- int idx = findOnTabletModeChangedListenerLocked(listener);
- if (idx >= 0) {
- OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
- d.removeCallbacksAndMessages(null);
- }
- }
- }
-
- private void initializeTabletModeListenerLocked() {
- final TabletModeChangedListener listener = new TabletModeChangedListener();
- try {
- mIm.registerTabletModeChangedListener(listener);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- mTabletModeChangedListener = listener;
- mOnTabletModeChangedListeners = new ArrayList<>();
- }
-
- private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
- final int N = mOnTabletModeChangedListeners.size();
- for (int i = 0; i < N; i++) {
- if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
- return i;
- }
- }
- return -1;
+ mGlobal.unregisterOnTabletModeChangedListener(listener);
}
/**
@@ -1389,11 +1223,7 @@
* @hide
*/
public InputSensorInfo[] getSensorList(int deviceId) {
- try {
- return mIm.getSensorList(deviceId);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return mGlobal.getSensorList(deviceId);
}
/**
@@ -1403,12 +1233,8 @@
*/
public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
int maxBatchReportLatencyUs) {
- try {
- return mIm.enableSensor(deviceId, sensorType, samplingPeriodUs,
- maxBatchReportLatencyUs);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return mGlobal.enableSensor(deviceId, sensorType, samplingPeriodUs,
+ maxBatchReportLatencyUs);
}
/**
@@ -1417,11 +1243,7 @@
* @hide
*/
public void disableSensor(int deviceId, int sensorType) {
- try {
- mIm.disableSensor(deviceId, sensorType);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ mGlobal.disableSensor(deviceId, sensorType);
}
/**
@@ -1430,11 +1252,7 @@
* @hide
*/
public boolean flushSensor(int deviceId, int sensorType) {
- try {
- return mIm.flushSensor(deviceId, sensorType);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return mGlobal.flushSensor(deviceId, sensorType);
}
/**
@@ -1443,11 +1261,7 @@
* @hide
*/
public boolean registerSensorListener(IInputSensorEventListener listener) {
- try {
- return mIm.registerSensorListener(listener);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return mGlobal.registerSensorListener(listener);
}
/**
@@ -1456,11 +1270,7 @@
* @hide
*/
public void unregisterSensorListener(IInputSensorEventListener listener) {
- try {
- mIm.unregisterSensorListener(listener);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ mGlobal.unregisterSensorListener(listener);
}
/**
@@ -1543,133 +1353,7 @@
*/
@Nullable
public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
- Objects.requireNonNull(display, "display should not be null");
-
- // Return the first valid USI version reported by any input device associated with
- // the display.
- synchronized (mInputDevicesLock) {
- populateInputDevicesLocked();
-
- for (int i = 0; i < mInputDevices.size(); i++) {
- final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
- if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
- if (device.getHostUsiVersion() != null) {
- return device.getHostUsiVersion();
- }
- }
- }
- }
-
- // If there are no input devices that report a valid USI version, see if there is a config
- // that specifies the USI version for the display. This is to handle cases where the USI
- // input device is not registered by the kernel/driver all the time.
- try {
- return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- private void populateInputDevicesLocked() {
- if (mInputDevicesChangedListener == null) {
- final InputDevicesChangedListener listener = new InputDevicesChangedListener();
- try {
- mIm.registerInputDevicesChangedListener(listener);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- mInputDevicesChangedListener = listener;
- }
-
- if (mInputDevices == null) {
- final int[] ids;
- try {
- ids = mIm.getInputDeviceIds();
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
- mInputDevices = new SparseArray<>();
- for (int id : ids) {
- mInputDevices.put(id, null);
- }
- }
- }
-
- private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
- if (DEBUG) {
- Log.d(TAG, "Received input devices changed.");
- }
-
- synchronized (mInputDevicesLock) {
- for (int i = mInputDevices.size(); --i > 0; ) {
- final int deviceId = mInputDevices.keyAt(i);
- if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
- if (DEBUG) {
- Log.d(TAG, "Device removed: " + deviceId);
- }
- mInputDevices.removeAt(i);
- sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
- }
- }
-
- for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
- final int deviceId = deviceIdAndGeneration[i];
- int index = mInputDevices.indexOfKey(deviceId);
- if (index >= 0) {
- final InputDevice device = mInputDevices.valueAt(index);
- if (device != null) {
- final int generation = deviceIdAndGeneration[i + 1];
- if (device.getGeneration() != generation) {
- if (DEBUG) {
- Log.d(TAG, "Device changed: " + deviceId);
- }
- mInputDevices.setValueAt(index, null);
- sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
- }
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "Device added: " + deviceId);
- }
- mInputDevices.put(deviceId, null);
- sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
- }
- }
- }
- }
-
- private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
- final int numListeners = mInputDeviceListeners.size();
- for (int i = 0; i < numListeners; i++) {
- InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
- listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
- }
- }
-
- private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
- for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
- if (deviceIdAndGeneration[i] == deviceId) {
- return true;
- }
- }
- return false;
- }
-
-
- private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
- if (DEBUG) {
- Log.d(TAG, "Received tablet mode changed: "
- + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
- }
- synchronized (mTabletModeLock) {
- final int numListeners = mOnTabletModeChangedListeners.size();
- for (int i = 0; i < numListeners; i++) {
- OnTabletModeChangedListenerDelegate listener =
- mOnTabletModeChangedListeners.get(i);
- listener.sendTabletModeChanged(whenNanos, inTabletMode);
- }
- }
+ return mGlobal.getHostUsiVersion(display);
}
/**
@@ -1795,10 +1479,7 @@
*/
@NonNull
public SensorManager getInputDeviceSensorManager(int deviceId) {
- if (mInputDeviceSensorManager == null) {
- mInputDeviceSensorManager = new InputDeviceSensorManager(this);
- }
- return mInputDeviceSensorManager.getSensorManager(deviceId);
+ return mGlobal.getInputDeviceSensorManager(deviceId);
}
/**
@@ -1808,15 +1489,7 @@
*/
@NonNull
public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
- if (!hasBattery) {
- return new LocalBatteryState();
- }
- try {
- final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
- return new LocalBatteryState(state.isPresent, state.status, state.capacity);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return mGlobal.getInputDeviceBatteryState(deviceId, hasBattery);
}
/**
@@ -1952,49 +1625,7 @@
*/
public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
@NonNull InputDeviceBatteryListener listener) {
- Objects.requireNonNull(executor, "executor should not be null");
- Objects.requireNonNull(listener, "listener should not be null");
-
- synchronized (mBatteryListenersLock) {
- if (mBatteryListeners == null) {
- mBatteryListeners = new SparseArray<>();
- mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
- }
- RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
- if (listenersForDevice == null) {
- // The deviceId is currently not being monitored for battery changes.
- // Start monitoring the device.
- listenersForDevice = new RegisteredBatteryListeners();
- mBatteryListeners.put(deviceId, listenersForDevice);
- try {
- mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- // The deviceId is already being monitored for battery changes.
- // Ensure that the listener is not already registered.
- final int numDelegates = listenersForDevice.mDelegates.size();
- for (int i = 0; i < numDelegates; i++) {
- InputDeviceBatteryListener registeredListener =
- listenersForDevice.mDelegates.get(i).mListener;
- if (Objects.equals(listener, registeredListener)) {
- throw new IllegalArgumentException(
- "Attempting to register an InputDeviceBatteryListener that has "
- + "already been registered for deviceId: "
- + deviceId);
- }
- }
- }
- final InputDeviceBatteryListenerDelegate delegate =
- new InputDeviceBatteryListenerDelegate(listener, executor);
- listenersForDevice.mDelegates.add(delegate);
-
- // Notify the listener immediately if we already have the latest battery state.
- if (listenersForDevice.mInputDeviceBatteryState != null) {
- delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
- }
- }
+ mGlobal.addInputDeviceBatteryListener(deviceId, executor, listener);
}
/**
@@ -2004,44 +1635,7 @@
*/
public void removeInputDeviceBatteryListener(int deviceId,
@NonNull InputDeviceBatteryListener listener) {
- Objects.requireNonNull(listener, "listener should not be null");
-
- synchronized (mBatteryListenersLock) {
- if (mBatteryListeners == null) {
- return;
- }
- RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
- if (listenersForDevice == null) {
- // The deviceId is not currently being monitored.
- return;
- }
- final List<InputDeviceBatteryListenerDelegate> delegates =
- listenersForDevice.mDelegates;
- for (int i = 0; i < delegates.size();) {
- if (Objects.equals(listener, delegates.get(i).mListener)) {
- delegates.remove(i);
- continue;
- }
- i++;
- }
- if (!delegates.isEmpty()) {
- return;
- }
-
- // There are no more battery listeners for this deviceId. Stop monitoring this device.
- mBatteryListeners.remove(deviceId);
- try {
- mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- if (mBatteryListeners.size() == 0) {
- // There are no more devices being monitored, so the registered
- // IInputDeviceBatteryListener will be automatically dropped by the server.
- mBatteryListeners = null;
- mInputDeviceBatteryListener = null;
- }
- }
+ mGlobal.removeInputDeviceBatteryListener(deviceId, listener);
}
/**
@@ -2067,30 +1661,7 @@
@RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
public void registerKeyboardBacklightListener(@NonNull Executor executor,
@NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
- Objects.requireNonNull(executor, "executor should not be null");
- Objects.requireNonNull(listener, "listener should not be null");
-
- synchronized (mKeyboardBacklightListenerLock) {
- if (mKeyboardBacklightListener == null) {
- mKeyboardBacklightListeners = new ArrayList<>();
- mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
-
- try {
- mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- final int numListeners = mKeyboardBacklightListeners.size();
- for (int i = 0; i < numListeners; i++) {
- if (mKeyboardBacklightListeners.get(i).mListener == listener) {
- throw new IllegalArgumentException("Listener has already been registered!");
- }
- }
- KeyboardBacklightListenerDelegate delegate =
- new KeyboardBacklightListenerDelegate(listener, executor);
- mKeyboardBacklightListeners.add(delegate);
- }
+ mGlobal.registerKeyboardBacklightListener(executor, listener);
}
/**
@@ -2103,23 +1674,7 @@
@RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
public void unregisterKeyboardBacklightListener(
@NonNull KeyboardBacklightListener listener) {
- Objects.requireNonNull(listener, "listener should not be null");
-
- synchronized (mKeyboardBacklightListenerLock) {
- if (mKeyboardBacklightListeners == null) {
- return;
- }
- mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
- if (mKeyboardBacklightListeners.isEmpty()) {
- try {
- mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- mKeyboardBacklightListeners = null;
- mKeyboardBacklightListener = null;
- }
- }
+ mGlobal.unregisterKeyboardBacklightListener(listener);
}
/**
@@ -2149,7 +1704,7 @@
public interface InputDeviceListener {
/**
* Called whenever an input device has been added to the system.
- * Use {@link InputManager#getInputDevice} to get more information about the device.
+ * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
*
* @param deviceId The id of the input device that was added.
*/
@@ -2172,37 +1727,6 @@
void onInputDeviceChanged(int deviceId);
}
- private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
- @Override
- public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
- InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
- }
- }
-
- private static final class InputDeviceListenerDelegate extends Handler {
- public final InputDeviceListener mListener;
-
- public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
- super(handler != null ? handler.getLooper() : Looper.myLooper());
- mListener = listener;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DEVICE_ADDED:
- mListener.onInputDeviceAdded(msg.arg1);
- break;
- case MSG_DEVICE_REMOVED:
- mListener.onInputDeviceRemoved(msg.arg1);
- break;
- case MSG_DEVICE_CHANGED:
- mListener.onInputDeviceChanged(msg.arg1);
- break;
- }
- }
- }
-
/** @hide */
public interface OnTabletModeChangedListener {
/**
@@ -2235,170 +1759,4 @@
void onKeyboardBacklightChanged(
int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
}
-
- private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
- @Override
- public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
- InputManager.this.onTabletModeChanged(whenNanos, inTabletMode);
- }
- }
-
- private static final class OnTabletModeChangedListenerDelegate extends Handler {
- private static final int MSG_TABLET_MODE_CHANGED = 0;
-
- public final OnTabletModeChangedListener mListener;
-
- public OnTabletModeChangedListenerDelegate(
- OnTabletModeChangedListener listener, Handler handler) {
- super(handler != null ? handler.getLooper() : Looper.myLooper());
- mListener = listener;
- }
-
- public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = (int) whenNanos;
- args.argi2 = (int) (whenNanos >> 32);
- args.arg1 = inTabletMode;
- obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_TABLET_MODE_CHANGED) {
- SomeArgs args = (SomeArgs) msg.obj;
- long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
- boolean inTabletMode = (boolean) args.arg1;
- mListener.onTabletModeChanged(whenNanos, inTabletMode);
- }
- }
- }
-
- // Implementation of the android.hardware.BatteryState interface used to report the battery
- // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
- private static final class LocalBatteryState extends BatteryState {
- private final boolean mIsPresent;
- private final int mStatus;
- private final float mCapacity;
-
- LocalBatteryState() {
- this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
- }
-
- LocalBatteryState(boolean isPresent, int status, float capacity) {
- mIsPresent = isPresent;
- mStatus = status;
- mCapacity = capacity;
- }
-
- @Override
- public boolean isPresent() {
- return mIsPresent;
- }
-
- @Override
- public int getStatus() {
- return mStatus;
- }
-
- @Override
- public float getCapacity() {
- return mCapacity;
- }
- }
-
- private static final class RegisteredBatteryListeners {
- final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
- IInputDeviceBatteryState mInputDeviceBatteryState;
- }
-
- private static final class InputDeviceBatteryListenerDelegate {
- final InputDeviceBatteryListener mListener;
- final Executor mExecutor;
-
- InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
- mListener = listener;
- mExecutor = executor;
- }
-
- void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
- mExecutor.execute(() ->
- mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
- new LocalBatteryState(state.isPresent, state.status, state.capacity)));
- }
- }
-
- private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
- @Override
- public void onBatteryStateChanged(IInputDeviceBatteryState state) {
- synchronized (mBatteryListenersLock) {
- if (mBatteryListeners == null) return;
- final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
- if (entry == null) return;
-
- entry.mInputDeviceBatteryState = state;
- final int numDelegates = entry.mDelegates.size();
- for (int i = 0; i < numDelegates; i++) {
- entry.mDelegates.get(i)
- .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
- }
- }
- }
- }
-
- // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
- // the keyboard backlight state via the KeyboardBacklightListener interfaces.
- private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
-
- private final int mBrightnessLevel;
- private final int mMaxBrightnessLevel;
-
- LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
- mBrightnessLevel = brightnessLevel;
- mMaxBrightnessLevel = maxBrightnessLevel;
- }
-
- @Override
- public int getBrightnessLevel() {
- return mBrightnessLevel;
- }
-
- @Override
- public int getMaxBrightnessLevel() {
- return mMaxBrightnessLevel;
- }
- }
-
- private static final class KeyboardBacklightListenerDelegate {
- final KeyboardBacklightListener mListener;
- final Executor mExecutor;
-
- KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
- mListener = listener;
- mExecutor = executor;
- }
-
- void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
- boolean isTriggeredByKeyPress) {
- mExecutor.execute(() ->
- mListener.onKeyboardBacklightChanged(deviceId,
- new LocalKeyboardBacklightState(state.brightnessLevel,
- state.maxBrightnessLevel), isTriggeredByKeyPress));
- }
- }
-
- private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
-
- @Override
- public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
- boolean isTriggeredByKeyPress) {
- synchronized (mKeyboardBacklightListenerLock) {
- if (mKeyboardBacklightListeners == null) return;
- final int numListeners = mKeyboardBacklightListeners.size();
- for (int i = 0; i < numListeners; i++) {
- mKeyboardBacklightListeners.get(i)
- .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
- }
- }
- }
- }
}
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 82dddfc..524d820 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -16,18 +16,74 @@
package android.hardware.input;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManager.InputDeviceBatteryListener;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.OnTabletModeChangedListener;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Manages communication with the input manager service on behalf of
- * an application process. You're probably looking for {@link InputManager}.
+ * an application process. You're probably looking for {@link InputManager}.
*
* @hide
*/
public final class InputManagerGlobal {
private static final String TAG = "InputManagerGlobal";
+ // To enable these logs, run: 'adb shell setprop log.tag.InputManagerGlobal DEBUG'
+ // (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ @GuardedBy("mInputDeviceListeners")
+ @Nullable private SparseArray<InputDevice> mInputDevices;
+ @GuardedBy("mInputDeviceListeners")
+ @Nullable private InputDevicesChangedListener mInputDevicesChangedListener;
+ @GuardedBy("mInputDeviceListeners")
+ private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
+
+ @GuardedBy("mOnTabletModeChangedListeners")
+ private final ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners =
+ new ArrayList<>();
+
+ private final Object mBatteryListenersLock = new Object();
+ // Maps a deviceId whose battery is currently being monitored to an entry containing the
+ // registered listeners for that device.
+ @GuardedBy("mBatteryListenersLock")
+ @Nullable private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+ @GuardedBy("mBatteryListenersLock")
+ @Nullable private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
+ private final Object mKeyboardBacklightListenerLock = new Object();
+ @GuardedBy("mKeyboardBacklightListenerLock")
+ @Nullable private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
+ @GuardedBy("mKeyboardBacklightListenerLock")
+ @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;
+
+ @Nullable private InputDeviceSensorManager mInputDeviceSensorManager;
private static InputManagerGlobal sInstance;
@@ -79,4 +135,772 @@
sInstance = null;
}
}
+
+ /**
+ * @see InputManager#getInputDevice(int)
+ */
+ @Nullable
+ public InputDevice getInputDevice(int id) {
+ synchronized (mInputDeviceListeners) {
+ populateInputDevicesLocked();
+
+ int index = mInputDevices.indexOfKey(id);
+ if (index < 0) {
+ return null;
+ }
+
+ InputDevice inputDevice = mInputDevices.valueAt(index);
+ if (inputDevice == null) {
+ try {
+ inputDevice = mIm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ if (inputDevice != null) {
+ mInputDevices.setValueAt(index, inputDevice);
+ }
+ }
+ return inputDevice;
+ }
+ }
+
+ @GuardedBy("mInputDeviceListeners")
+ private void populateInputDevicesLocked() {
+ if (mInputDevicesChangedListener == null) {
+ final InputDevicesChangedListener
+ listener = new InputDevicesChangedListener();
+ try {
+ mIm.registerInputDevicesChangedListener(listener);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ mInputDevicesChangedListener = listener;
+ }
+
+ if (mInputDevices == null) {
+ final int[] ids;
+ try {
+ ids = mIm.getInputDeviceIds();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ mInputDevices = new SparseArray<>();
+ for (int id : ids) {
+ mInputDevices.put(id, null);
+ }
+ }
+ }
+
+ private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+ @Override
+ public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+ InputManagerGlobal.this.onInputDevicesChanged(deviceIdAndGeneration);
+ }
+ }
+
+ private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+ if (DEBUG) {
+ Log.d(TAG, "Received input devices changed.");
+ }
+
+ synchronized (mInputDeviceListeners) {
+ for (int i = mInputDevices.size(); --i > 0; ) {
+ final int deviceId = mInputDevices.keyAt(i);
+ if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+ if (DEBUG) {
+ Log.d(TAG, "Device removed: " + deviceId);
+ }
+ mInputDevices.removeAt(i);
+ sendMessageToInputDeviceListenersLocked(
+ InputDeviceListenerDelegate.MSG_DEVICE_REMOVED, deviceId);
+ }
+ }
+
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ final int deviceId = deviceIdAndGeneration[i];
+ int index = mInputDevices.indexOfKey(deviceId);
+ if (index >= 0) {
+ final InputDevice device = mInputDevices.valueAt(index);
+ if (device != null) {
+ final int generation = deviceIdAndGeneration[i + 1];
+ if (device.getGeneration() != generation) {
+ if (DEBUG) {
+ Log.d(TAG, "Device changed: " + deviceId);
+ }
+ mInputDevices.setValueAt(index, null);
+ sendMessageToInputDeviceListenersLocked(
+ InputDeviceListenerDelegate.MSG_DEVICE_CHANGED, deviceId);
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Device added: " + deviceId);
+ }
+ mInputDevices.put(deviceId, null);
+ sendMessageToInputDeviceListenersLocked(
+ InputDeviceListenerDelegate.MSG_DEVICE_ADDED, deviceId);
+ }
+ }
+ }
+ }
+
+ private static final class InputDeviceListenerDelegate extends Handler {
+ public final InputDeviceListener mListener;
+ static final int MSG_DEVICE_ADDED = 1;
+ static final int MSG_DEVICE_REMOVED = 2;
+ static final int MSG_DEVICE_CHANGED = 3;
+
+ InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper());
+ mListener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DEVICE_ADDED:
+ mListener.onInputDeviceAdded(msg.arg1);
+ break;
+ case MSG_DEVICE_REMOVED:
+ mListener.onInputDeviceRemoved(msg.arg1);
+ break;
+ case MSG_DEVICE_CHANGED:
+ mListener.onInputDeviceChanged(msg.arg1);
+ break;
+ }
+ }
+ }
+
+ private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ if (deviceIdAndGeneration[i] == deviceId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @GuardedBy("mInputDeviceListeners")
+ private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+ listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+ }
+ }
+
+ /**
+ * @see InputManager#registerInputDeviceListener
+ */
+ void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDeviceListeners) {
+ populateInputDevicesLocked();
+ int index = findInputDeviceListenerLocked(listener);
+ if (index < 0) {
+ mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#unregisterInputDeviceListener
+ */
+ void unregisterInputDeviceListener(InputDeviceListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDeviceListeners) {
+ int index = findInputDeviceListenerLocked(listener);
+ if (index >= 0) {
+ InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
+ d.removeCallbacksAndMessages(null);
+ mInputDeviceListeners.remove(index);
+ }
+ }
+ }
+
+ @GuardedBy("mInputDeviceListeners")
+ private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mInputDeviceListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @see InputManager#getInputDeviceIds
+ */
+ public int[] getInputDeviceIds() {
+ synchronized (mInputDeviceListeners) {
+ populateInputDevicesLocked();
+
+ final int count = mInputDevices.size();
+ final int[] ids = new int[count];
+ for (int i = 0; i < count; i++) {
+ ids[i] = mInputDevices.keyAt(i);
+ }
+ return ids;
+ }
+ }
+
+ /**
+ * @see InputManager#getInputDeviceByDescriptor
+ */
+ InputDevice getInputDeviceByDescriptor(String descriptor) {
+ if (descriptor == null) {
+ throw new IllegalArgumentException("descriptor must not be null.");
+ }
+
+ synchronized (mInputDeviceListeners) {
+ populateInputDevicesLocked();
+
+ int numDevices = mInputDevices.size();
+ for (int i = 0; i < numDevices; i++) {
+ InputDevice inputDevice = mInputDevices.valueAt(i);
+ if (inputDevice == null) {
+ int id = mInputDevices.keyAt(i);
+ try {
+ inputDevice = mIm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ if (inputDevice == null) {
+ continue;
+ }
+ mInputDevices.setValueAt(i, inputDevice);
+ }
+ if (descriptor.equals(inputDevice.getDescriptor())) {
+ return inputDevice;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @see InputManager#getHostUsiVersion
+ */
+ @Nullable
+ HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+ Objects.requireNonNull(display, "display should not be null");
+
+ // Return the first valid USI version reported by any input device associated with
+ // the display.
+ synchronized (mInputDeviceListeners) {
+ populateInputDevicesLocked();
+
+ for (int i = 0; i < mInputDevices.size(); i++) {
+ final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+ if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+ if (device.getHostUsiVersion() != null) {
+ return device.getHostUsiVersion();
+ }
+ }
+ }
+ }
+
+ // If there are no input devices that report a valid USI version, see if there is a config
+ // that specifies the USI version for the display. This is to handle cases where the USI
+ // input device is not registered by the kernel/driver all the time.
+ try {
+ return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+ if (DEBUG) {
+ Log.d(TAG, "Received tablet mode changed: "
+ + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
+ }
+ synchronized (mOnTabletModeChangedListeners) {
+ final int numListeners = mOnTabletModeChangedListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ OnTabletModeChangedListenerDelegate listener =
+ mOnTabletModeChangedListeners.get(i);
+ listener.sendTabletModeChanged(whenNanos, inTabletMode);
+ }
+ }
+ }
+
+ private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
+ @Override
+ public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+ InputManagerGlobal.this.onTabletModeChanged(whenNanos, inTabletMode);
+ }
+ }
+
+ private static final class OnTabletModeChangedListenerDelegate extends Handler {
+ private static final int MSG_TABLET_MODE_CHANGED = 0;
+
+ public final OnTabletModeChangedListener mListener;
+
+ OnTabletModeChangedListenerDelegate(
+ OnTabletModeChangedListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper());
+ mListener = listener;
+ }
+
+ public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = (int) whenNanos;
+ args.argi2 = (int) (whenNanos >> 32);
+ args.arg1 = inTabletMode;
+ obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_TABLET_MODE_CHANGED) {
+ SomeArgs args = (SomeArgs) msg.obj;
+ long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
+ boolean inTabletMode = (boolean) args.arg1;
+ mListener.onTabletModeChanged(whenNanos, inTabletMode);
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#registerInputDeviceListener(InputDeviceListener, Handler)
+ */
+ void registerOnTabletModeChangedListener(
+ OnTabletModeChangedListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized (mOnTabletModeChangedListeners) {
+ if (mOnTabletModeChangedListeners == null) {
+ initializeTabletModeListenerLocked();
+ }
+ int idx = findOnTabletModeChangedListenerLocked(listener);
+ if (idx < 0) {
+ OnTabletModeChangedListenerDelegate d =
+ new OnTabletModeChangedListenerDelegate(listener, handler);
+ mOnTabletModeChangedListeners.add(d);
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#unregisterOnTabletModeChangedListener(OnTabletModeChangedListener)
+ */
+ void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized (mOnTabletModeChangedListeners) {
+ int idx = findOnTabletModeChangedListenerLocked(listener);
+ if (idx >= 0) {
+ OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
+ d.removeCallbacksAndMessages(null);
+ }
+ }
+ }
+
+ @GuardedBy("mOnTabletModeChangedListeners")
+ private void initializeTabletModeListenerLocked() {
+ final TabletModeChangedListener listener = new TabletModeChangedListener();
+ try {
+ mIm.registerTabletModeChangedListener(listener);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @GuardedBy("mOnTabletModeChangedListeners")
+ private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
+ final int n = mOnTabletModeChangedListeners.size();
+ for (int i = 0; i < n; i++) {
+ if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static final class RegisteredBatteryListeners {
+ final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+ IInputDeviceBatteryState mInputDeviceBatteryState;
+ }
+
+ private static final class InputDeviceBatteryListenerDelegate {
+ final InputDeviceBatteryListener mListener;
+ final Executor mExecutor;
+
+ InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
+ mExecutor.execute(() ->
+ mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
+ new LocalBatteryState(state.isPresent, state.status, state.capacity)));
+ }
+ }
+
+ /**
+ * @see InputManager#addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+ */
+ void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ mBatteryListeners = new SparseArray<>();
+ mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is currently not being monitored for battery changes.
+ // Start monitoring the device.
+ listenersForDevice = new RegisteredBatteryListeners();
+ mBatteryListeners.put(deviceId, listenersForDevice);
+ try {
+ mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ // The deviceId is already being monitored for battery changes.
+ // Ensure that the listener is not already registered.
+ final int numDelegates = listenersForDevice.mDelegates.size();
+ for (int i = 0; i < numDelegates; i++) {
+ InputDeviceBatteryListener registeredListener =
+ listenersForDevice.mDelegates.get(i).mListener;
+ if (Objects.equals(listener, registeredListener)) {
+ throw new IllegalArgumentException(
+ "Attempting to register an InputDeviceBatteryListener that has "
+ + "already been registered for deviceId: "
+ + deviceId);
+ }
+ }
+ }
+ final InputDeviceBatteryListenerDelegate delegate =
+ new InputDeviceBatteryListenerDelegate(listener, executor);
+ listenersForDevice.mDelegates.add(delegate);
+
+ // Notify the listener immediately if we already have the latest battery state.
+ if (listenersForDevice.mInputDeviceBatteryState != null) {
+ delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+ */
+ void removeInputDeviceBatteryListener(int deviceId,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ return;
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is not currently being monitored.
+ return;
+ }
+ final List<InputDeviceBatteryListenerDelegate> delegates =
+ listenersForDevice.mDelegates;
+ for (int i = 0; i < delegates.size();) {
+ if (Objects.equals(listener, delegates.get(i).mListener)) {
+ delegates.remove(i);
+ continue;
+ }
+ i++;
+ }
+ if (!delegates.isEmpty()) {
+ return;
+ }
+
+ // There are no more battery listeners for this deviceId. Stop monitoring this device.
+ mBatteryListeners.remove(deviceId);
+ try {
+ mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (mBatteryListeners.size() == 0) {
+ // There are no more devices being monitored, so the registered
+ // IInputDeviceBatteryListener will be automatically dropped by the server.
+ mBatteryListeners = null;
+ mInputDeviceBatteryListener = null;
+ }
+ }
+ }
+
+ private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+ @Override
+ public void onBatteryStateChanged(IInputDeviceBatteryState state) {
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) return;
+ final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
+ if (entry == null) return;
+
+ entry.mInputDeviceBatteryState = state;
+ final int numDelegates = entry.mDelegates.size();
+ for (int i = 0; i < numDelegates; i++) {
+ entry.mDelegates.get(i)
+ .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#getInputDeviceBatteryState(int, boolean)
+ */
+ @NonNull
+ BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
+ if (!hasBattery) {
+ return new LocalBatteryState();
+ }
+ try {
+ final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
+ return new LocalBatteryState(state.isPresent, state.status, state.capacity);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ // Implementation of the android.hardware.BatteryState interface used to report the battery
+ // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
+ private static final class LocalBatteryState extends BatteryState {
+ private final boolean mIsPresent;
+ private final int mStatus;
+ private final float mCapacity;
+
+ LocalBatteryState() {
+ this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
+ }
+
+ LocalBatteryState(boolean isPresent, int status, float capacity) {
+ mIsPresent = isPresent;
+ mStatus = status;
+ mCapacity = capacity;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return mIsPresent;
+ }
+
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public float getCapacity() {
+ return mCapacity;
+ }
+ }
+
+ private static final class KeyboardBacklightListenerDelegate {
+ final InputManager.KeyboardBacklightListener mListener;
+ final Executor mExecutor;
+
+ KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
+ boolean isTriggeredByKeyPress) {
+ mExecutor.execute(() ->
+ mListener.onKeyboardBacklightChanged(deviceId,
+ new LocalKeyboardBacklightState(state.brightnessLevel,
+ state.maxBrightnessLevel), isTriggeredByKeyPress));
+ }
+ }
+
+ private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
+
+ @Override
+ public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
+ boolean isTriggeredByKeyPress) {
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListeners == null) return;
+ final int numListeners = mKeyboardBacklightListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ mKeyboardBacklightListeners.get(i)
+ .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
+ }
+ }
+ }
+ }
+
+ // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
+ // the keyboard backlight state via the KeyboardBacklightListener interfaces.
+ private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
+
+ private final int mBrightnessLevel;
+ private final int mMaxBrightnessLevel;
+
+ LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
+ mBrightnessLevel = brightnessLevel;
+ mMaxBrightnessLevel = maxBrightnessLevel;
+ }
+
+ @Override
+ public int getBrightnessLevel() {
+ return mBrightnessLevel;
+ }
+
+ @Override
+ public int getMaxBrightnessLevel() {
+ return mMaxBrightnessLevel;
+ }
+ }
+
+ /**
+ * @see InputManager#registerKeyboardBacklightListener(Executor, KeyboardBacklightListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ void registerKeyboardBacklightListener(@NonNull Executor executor,
+ @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListener == null) {
+ mKeyboardBacklightListeners = new ArrayList<>();
+ mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
+
+ try {
+ mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ final int numListeners = mKeyboardBacklightListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mKeyboardBacklightListeners.get(i).mListener == listener) {
+ throw new IllegalArgumentException("Listener has already been registered!");
+ }
+ }
+ KeyboardBacklightListenerDelegate delegate =
+ new KeyboardBacklightListenerDelegate(listener, executor);
+ mKeyboardBacklightListeners.add(delegate);
+ }
+ }
+
+ /**
+ * @see InputManager#unregisterKeyboardBacklightListener(KeyboardBacklightListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ void unregisterKeyboardBacklightListener(
+ @NonNull KeyboardBacklightListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListeners == null) {
+ return;
+ }
+ mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
+ if (mKeyboardBacklightListeners.isEmpty()) {
+ try {
+ mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mKeyboardBacklightListeners = null;
+ mKeyboardBacklightListener = null;
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#getInputDeviceSensorManager(int)
+ */
+ @NonNull
+ SensorManager getInputDeviceSensorManager(int deviceId) {
+ if (mInputDeviceSensorManager == null) {
+ mInputDeviceSensorManager = new InputDeviceSensorManager(this);
+ }
+ return mInputDeviceSensorManager.getSensorManager(deviceId);
+ }
+
+ /**
+ * @see InputManager#getSensorList(int)
+ */
+ InputSensorInfo[] getSensorList(int deviceId) {
+ try {
+ return mIm.getSensorList(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#enableSensor(int, int, int, int)
+ */
+ boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+ int maxBatchReportLatencyUs) {
+ try {
+ return mIm.enableSensor(deviceId, sensorType, samplingPeriodUs,
+ maxBatchReportLatencyUs);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#disableSensor(int, int)
+ */
+ void disableSensor(int deviceId, int sensorType) {
+ try {
+ mIm.disableSensor(deviceId, sensorType);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#flushSensor(int, int)
+ */
+ boolean flushSensor(int deviceId, int sensorType) {
+ try {
+ return mIm.flushSensor(deviceId, sensorType);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#registerSensorListener(IInputSensorEventListener)
+ */
+ boolean registerSensorListener(IInputSensorEventListener listener) {
+ try {
+ return mIm.registerSensorListener(listener);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#unregisterSensorListener(IInputSensorEventListener)
+ */
+ void unregisterSensorListener(IInputSensorEventListener listener) {
+ try {
+ mIm.unregisterSensorListener(listener);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index e63f57b..5593989 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -79,6 +79,11 @@
}
/**
+ * @hide
+ */
+ public static final String FAKE_HAL_ARCH = "injection";
+
+ /**
* Status code used when the operation succeeded
*/
public static final int STATUS_OK = 0;
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9689be2..244632a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -64,17 +64,27 @@
/**
* The product name for attestation. In non-default builds (like the AOSP build) the value of
* the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
- * and Keymint attestation would still attest to the product name, it's running on.
+ * and Keymint attestation would still attest to the product name which was provisioned.
* @hide
*/
@Nullable
@TestApi
- public static final String PRODUCT_FOR_ATTESTATION =
- getString("ro.product.name_for_attestation");
+ public static final String PRODUCT_FOR_ATTESTATION = getVendorDeviceIdProperty("name");
/** The name of the industrial design. */
public static final String DEVICE = getString("ro.product.device");
+ /**
+ * The device name for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'DEVICE' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the device name which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String DEVICE_FOR_ATTESTATION =
+ getVendorDeviceIdProperty("device");
+
/** The name of the underlying board, like "goldfish". */
public static final String BOARD = getString("ro.product.board");
@@ -97,19 +107,29 @@
/** The manufacturer of the product/hardware. */
public static final String MANUFACTURER = getString("ro.product.manufacturer");
+ /**
+ * The manufacturer name for attestation. In non-default builds (like the AOSP build) the value
+ * of the 'MANUFACTURER' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the manufacturer which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String MANUFACTURER_FOR_ATTESTATION =
+ getVendorDeviceIdProperty("manufacturer");
+
/** The consumer-visible brand with which the product/hardware will be associated, if any. */
public static final String BRAND = getString("ro.product.brand");
/**
* The product brand for attestation. In non-default builds (like the AOSP build) the value of
* the 'BRAND' system property may be different to the one provisioned to KeyMint,
- * and Keymint attestation would still attest to the product brand, it's running on.
+ * and Keymint attestation would still attest to the product brand which was provisioned.
* @hide
*/
@Nullable
@TestApi
- public static final String BRAND_FOR_ATTESTATION =
- getString("ro.product.brand_for_attestation");
+ public static final String BRAND_FOR_ATTESTATION = getVendorDeviceIdProperty("brand");
/** The end-user-visible name for the end product. */
public static final String MODEL = getString("ro.product.model");
@@ -117,13 +137,12 @@
/**
* The product model for attestation. In non-default builds (like the AOSP build) the value of
* the 'MODEL' system property may be different to the one provisioned to KeyMint,
- * and Keymint attestation would still attest to the product model, it's running on.
+ * and Keymint attestation would still attest to the product model which was provisioned.
* @hide
*/
@Nullable
@TestApi
- public static final String MODEL_FOR_ATTESTATION =
- getString("ro.product.model_for_attestation");
+ public static final String MODEL_FOR_ATTESTATION = getVendorDeviceIdProperty("model");
/** The manufacturer of the device's primary system-on-chip. */
@NonNull
@@ -1527,6 +1546,17 @@
private static String getString(String property) {
return SystemProperties.get(property, UNKNOWN);
}
+ /**
+ * Return attestation specific proerties.
+ * @param property model, name, brand, device or manufacturer.
+ * @return property value or UNKNOWN
+ */
+ private static String getVendorDeviceIdProperty(String property) {
+ String attestProp = getString(
+ TextUtils.formatSimple("ro.product.%s_for_attestation", property));
+ return attestProp.equals(UNKNOWN)
+ ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN;
+ }
private static String[] getStringList(String property, String separator) {
String value = SystemProperties.get(property);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ac1583a..b2208d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -365,6 +365,11 @@
public static final int LAST_APPLICATION_CACHE_GID = 29999;
/**
+ * An invalid PID value.
+ */
+ public static final int INVALID_PID = -1;
+
+ /**
* Standard priority of application threads.
* Use with {@link #setThreadPriority(int)} and
* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 290f929..86e678d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5601,16 +5601,15 @@
*
* @see #KEY_RESTRICTIONS_PENDING
*
- * @deprecated Use
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+ * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+ * set by all managing apps, use
* {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
- * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
- * possible for there to be multiple managing agents on the device with the ability to set
- * restrictions. This API will only to return the restrictions set by device policy controllers
- * (DPCs)
*
* @see DevicePolicyManager
*/
- @Deprecated
@WorkerThread
@UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public Bundle getApplicationRestrictions(String packageName) {
@@ -5623,12 +5622,15 @@
}
/**
- * @deprecated Use
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+ * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+ * set by all managing apps, use
* {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
*
* @hide
*/
- @Deprecated
@WorkerThread
public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07d265b..127c7a0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9541,6 +9541,14 @@
public static final String SCREENSAVER_COMPLICATIONS_ENABLED =
"screensaver_complications_enabled";
+ /**
+ * Whether home controls are enabled to be shown over the screensaver by the user.
+ *
+ * @hide
+ */
+ public static final String SCREENSAVER_HOME_CONTROLS_ENABLED =
+ "screensaver_home_controls_enabled";
+
/**
* Default, indicates that the user has not yet started the dock setup flow.
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 6e4535b..5469916 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -27,6 +27,8 @@
import android.util.Log;
import android.view.WindowManager;
+import java.util.concurrent.Executor;
+
/**
* Basic implementation of for {@link IDreamOverlay} for testing.
@@ -40,6 +42,12 @@
// The last client that started dreaming and hasn't ended
private OverlayClient mCurrentClient;
+ /**
+ * Executor used to run callbacks that subclasses will implement. Any calls coming over Binder
+ * from {@link OverlayClient} should perform the work they need to do on this executor.
+ */
+ private Executor mExecutor;
+
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
@@ -61,8 +69,6 @@
mService.startDream(this, params);
}
-
-
@Override
public void wakeUp() {
mService.wakeUp(this, () -> {
@@ -97,12 +103,20 @@
}
private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
- endDream(mCurrentClient);
- mCurrentClient = client;
- onStartDream(params);
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> {
+ endDreamInternal(mCurrentClient);
+ mCurrentClient = client;
+ onStartDream(params);
+ });
}
private void endDream(OverlayClient client) {
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> endDreamInternal(client));
+ }
+
+ private void endDreamInternal(OverlayClient client) {
if (client == null || client != mCurrentClient) {
return;
}
@@ -112,11 +126,14 @@
}
private void wakeUp(OverlayClient client, Runnable callback) {
- if (mCurrentClient != client) {
- return;
- }
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> {
+ if (mCurrentClient != client) {
+ return;
+ }
- onWakeUp(callback);
+ onWakeUp(callback);
+ });
}
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@@ -134,6 +151,25 @@
public DreamOverlayService() {
}
+ /**
+ * This constructor allows providing an executor to run callbacks on.
+ *
+ * @hide
+ */
+ public DreamOverlayService(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (mExecutor == null) {
+ // If no executor was provided, use the main executor. onCreate is the earliest time
+ // getMainExecutor is available.
+ mExecutor = getMainExecutor();
+ }
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -143,6 +179,10 @@
/**
* This method is overridden by implementations to handle when the dream has started and the
* window is ready to be interacted with.
+ *
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
+ *
* @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
* dream window.
*/
@@ -153,6 +193,9 @@
* to wakeup. This allows any overlay animations to run. By default, the method will invoke
* the callback immediately.
*
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
+ *
* @param onCompleteCallback The callback to trigger to notify the dream service that the
* overlay has completed waking up.
* @hide
@@ -164,6 +207,9 @@
/**
* This method is overridden by implementations to handle when the dream has ended. There may
* be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
+ *
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
*/
public void onEndDream() {
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index ce8af83..d0f3820 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1362,6 +1362,11 @@
if (!ActivityTaskManager.getService().startDreamActivity(i)) {
detach();
}
+ } catch (SecurityException e) {
+ Log.w(mTag,
+ "Received SecurityException trying to start DreamActivity. "
+ + "Aborting dream start.");
+ detach();
} catch (RemoteException e) {
Log.w(mTag, "Could not connect to activity task manager to start dream activity");
e.rethrowFromSystemServer();
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 644a2bf..0f3e8d1 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
-import android.app.compat.CompatChanges;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Binder;
@@ -100,7 +99,7 @@
public boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
- @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+ @Nullable PersistableBundle options) {
if (DEBUG) {
Slog.i(TAG, "#recognizeHotword");
}
@@ -132,18 +131,13 @@
* @param sharedMemory The unrestricted data blob to provide to the
* {@link VisualQueryDetectionService} and {@link HotwordDetectionService}. Use this to
* provide the hotword models data or other such data to the trusted process.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
- * Android Tiramisu or above and attempts to start a recognition when the detector is
- * not able based on the state. Because the caller receives updates via an asynchronous
- * callback and the state of the detector can change without caller's knowledge, a
- * checked exception is thrown.
* @throws IllegalStateException if this {@link HotwordDetector} wasn't specified to use a
* {@link HotwordDetectionService} or {@link VisualQueryDetectionService} when it was
* created.
*/
@Override
public void updateState(@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
+ @Nullable SharedMemory sharedMemory) {
if (DEBUG) {
Slog.d(TAG, "updateState()");
}
@@ -199,13 +193,9 @@
}
}
- protected void throwIfDetectorIsNoLongerActive() throws IllegalDetectorStateException {
+ protected void throwIfDetectorIsNoLongerActive() {
if (!mIsDetectorActive.get()) {
Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "attempting to use a destroyed detector which is no longer active");
- }
throw new IllegalStateException(
"attempting to use a destroyed detector which is no longer active");
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b1dc686..ffa15f0 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -805,11 +805,13 @@
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
if (moduleProperties == null) {
- List<SoundTrigger.ModuleProperties> modulePropList =
- mModelManagementService.listModuleProperties(identity);
- if (modulePropList.size() > 0) {
- moduleProperties = modulePropList.get(0);
- }
+ moduleProperties = mModelManagementService
+ .listModuleProperties(identity)
+ .stream()
+ .filter(prop -> !prop.getSupportedModelArch()
+ .equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
// (@atneya) intentionally let a null moduleProperties through until
// all CTS tests are fixed
}
@@ -825,28 +827,19 @@
/**
* {@inheritDoc}
*
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and this AlwaysOnHotwordDetector wasn't specified to use a
- * {@link HotwordDetectionService} when it was created. In addition, the exception can
- * be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
- * this AlwaysOnHotwordDetector wasn't specified to use a
- * {@link HotwordDetectionService} when it was created. In addition, the exception can
- * be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
+ * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
+ * {@link HotwordDetectionService} when it was created. In addition, if this
+ * AlwaysOnHotwordDetector is in an invalid or error state.
*/
@Override
public final void updateState(@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
+ @Nullable SharedMemory sharedMemory) {
synchronized (mLock) {
if (!mSupportSandboxedDetectionService) {
throw new IllegalStateException(
"updateState called, but it doesn't support hotword detection service");
}
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "updateState called on an invalid detector or error state");
- }
throw new IllegalStateException(
"updateState called on an invalid detector or error state");
}
@@ -867,17 +860,16 @@
@TestApi
public void overrideAvailability(int availability) {
synchronized (mLock) {
+ mAvailability = availability;
+ mIsAvailabilityOverriddenByTestApi = true;
// ENROLLED state requires there to be metadata about the sound model so a fake one
// is created.
- if (mKeyphraseMetadata == null && availability == STATE_KEYPHRASE_ENROLLED) {
+ if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) {
Set<Locale> fakeSupportedLocales = new HashSet<>();
fakeSupportedLocales.add(mLocale);
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
-
- mAvailability = availability;
- mIsAvailabilityOverriddenByTestApi = true;
notifyStateChangedLocked();
}
}
@@ -933,23 +925,14 @@
* @see #RECOGNITION_MODE_USER_IDENTIFICATION
* @see #RECOGNITION_MODE_VOICE_TRIGGER
*
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above. Because the caller receives availability updates via an asynchronous
- * callback, it may be due to the availability changing while this call is performed.
- * - Throws if the detector is in an invalid or error state.
- * This may happen if another detector has been instantiated or the
- * {@link VoiceInteractionService} hosting this detector has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 Android if the recognition isn't supported. Callers should only call this method
- * after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
- * avoid this exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
- * 33 if the detector is in an invalid or error state. This may happen if another
- * detector has been instantiated or the {@link VoiceInteractionService} hosting this
- * detector has been shut down.
+ * @throws UnsupportedOperationException if the keyphrase itself isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
- public @RecognitionModes
- int getSupportedRecognitionModes() throws IllegalDetectorStateException {
+ public @RecognitionModes int getSupportedRecognitionModes() {
if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()");
synchronized (mLock) {
return getSupportedRecognitionModesLocked();
@@ -957,22 +940,14 @@
}
@GuardedBy("mLock")
- private int getSupportedRecognitionModesLocked() throws IllegalDetectorStateException {
+ private int getSupportedRecognitionModesLocked() {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException("getSupportedRecognitionModes called on an"
- + " invalid detector or error state");
- }
throw new IllegalStateException(
"getSupportedRecognitionModes called on an invalid detector or error state");
}
// This method only makes sense if we can actually support a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException("Getting supported recognition modes for"
- + " the keyphrase is not supported");
- }
throw new UnsupportedOperationException(
"Getting supported recognition modes for the keyphrase is not supported");
}
@@ -1027,30 +1002,15 @@
* startRecognition request. This data is intended to provide additional parameters
* when starting the opaque sound model.
* @return Indicates whether the call succeeded or not.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and attempts to start a recognition when the detector is not able based on
- * the availability state. This can be thrown even if the state has been checked before
- * calling this method because the caller receives availability updates via an
- * asynchronous callback, it may be due to the availability changing while this call is
- * performed.
- * - Throws if the recognition isn't supported.
- * Callers should only call this method after a supported state callback on
- * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
- * - Also throws if the detector is in an invalid or error state.
- * This may happen if another detector has been instantiated or the
- * {@link VoiceInteractionService} hosting this detector has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 Android if the recognition isn't supported. Callers should only call this method
- * after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
- * avoid this exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
- * 33 if the detector is in an invalid or error state. This may happen if another
- * detector has been instantiated or the {@link VoiceInteractionService} hosting this
- * detector has been shut down.
+ * @throws UnsupportedOperationException if the recognition isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
- public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data)
- throws IllegalDetectorStateException {
+ public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) {
synchronized (mLock) {
return startRecognitionLocked(recognitionFlags, data)
== STATUS_OK;
@@ -1067,30 +1027,15 @@
*
* @param recognitionFlags The flags to control the recognition properties.
* @return Indicates whether the call succeeded or not.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and attempts to start a recognition when the detector is not able based on
- * the availability state. This can be thrown even if the state has been checked before
- * calling this method because the caller receives availability updates via an
- * asynchronous callback, it may be due to the availability changing while this call is
- * performed.
- * - Throws if the recognition isn't supported.
- * Callers should only call this method after a supported state callback on
- * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
- * - Also throws if the detector is in an invalid or error state.
- * This may happen if another detector has been instantiated or the
- * {@link VoiceInteractionService} hosting this detector has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 if the recognition isn't supported. Callers should only call this method after a
- * supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
- * exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid or error state. This may happen if another detector has
- * been instantiated or the {@link VoiceInteractionService} hosting this detector has
- * been shut down.
+ * @throws UnsupportedOperationException if the recognition isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
- public boolean startRecognition(@RecognitionFlags int recognitionFlags)
- throws IllegalDetectorStateException {
+ public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
synchronized (mLock) {
return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
@@ -1104,8 +1049,7 @@
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Override
- public boolean startRecognition()
- throws IllegalDetectorStateException {
+ public boolean startRecognition() {
return startRecognition(0);
}
@@ -1115,44 +1059,28 @@
* Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @return Indicates whether the call succeeded or not.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
- * API level 33 or above and attempts to stop a recognition when the detector is
- * not able based on the state. This can be thrown even if the state has been checked
- * before calling this method because the caller receives availability updates via an
- * asynchronous callback, it may be due to the availability changing while this call is
- * performed.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 if the recognition isn't supported. Callers should only call this method after a
- * supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
- * exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid or error state. This may happen if another detector has
- * been instantiated or the {@link VoiceInteractionService} hosting this detector has
- * been shut down.
+ * @throws UnsupportedOperationException if the recognition isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
// TODO: Remove this RequiresPermission since it isn't actually enforced. Also fix the javadoc
// about permissions enforcement (when it throws vs when it just returns false) for other
// methods in this class.
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Override
- public boolean stopRecognition() throws IllegalDetectorStateException {
+ public boolean stopRecognition() {
if (DBG) Slog.d(TAG, "stopRecognition()");
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "stopRecognition called on an invalid detector or error state");
- }
throw new IllegalStateException(
"stopRecognition called on an invalid detector or error state");
}
// Check if we can start/stop a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "Recognition for the given keyphrase is not supported");
- }
throw new UnsupportedOperationException(
"Recognition for the given keyphrase is not supported");
}
@@ -1177,28 +1105,18 @@
* - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
* if API is not supported by HAL
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * if the detector is in an invalid or error state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid or error state. This may happen if another detector has
- * been instantiated or the {@link VoiceInteractionService} hosting this detector has
- * been shut down.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
- public int setParameter(@ModelParams int modelParam, int value)
- throws IllegalDetectorStateException {
+ public int setParameter(@ModelParams int modelParam, int value) {
if (DBG) {
Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "setParameter called on an invalid detector or error state");
- }
throw new IllegalStateException(
"setParameter called on an invalid detector or error state");
}
@@ -1219,27 +1137,18 @@
*
* @param modelParam {@link ModelParams}
* @return value of parameter
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * if the detector is in an invalid or error state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
- * the detector is in an invalid or error state. This may happen if another detector has
- * been instantiated or the {@link VoiceInteractionService} hosting this detector has
- * been shut down.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
- public int getParameter(@ModelParams int modelParam) throws IllegalDetectorStateException {
+ public int getParameter(@ModelParams int modelParam) {
if (DBG) {
Slog.d(TAG, "getParameter(" + modelParam + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "getParameter called on an invalid detector or error state");
- }
throw new IllegalStateException(
"getParameter called on an invalid detector or error state");
}
@@ -1257,29 +1166,19 @@
*
* @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * if the detector is in an invalid or error state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
- * the detector is in an invalid or error state. This may happen if another detector has
- * been instantiated or the {@link VoiceInteractionService} hosting this detector has
- * been shut down.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Nullable
- public ModelParamRange queryParameter(@ModelParams int modelParam)
- throws IllegalDetectorStateException {
+ public ModelParamRange queryParameter(@ModelParams int modelParam) {
if (DBG) {
Slog.d(TAG, "queryParameter(" + modelParam + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "queryParameter called on an invalid detector or error state");
- }
throw new IllegalStateException(
"queryParameter called on an invalid detector or error state");
}
@@ -1296,25 +1195,15 @@
* otherwise {@link #createReEnrollIntent()} should be preferred.
*
* @return An {@link Intent} to start enrollment for the given keyphrase.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above.
- * - Thrown if managing they keyphrase isn't supported. Callers should only call this
- * method after a supported state callback on
- * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
- * - Thrown if the detector is in an invalid state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 if managing they keyphrase isn't supported. Callers should only call this method
- * after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
- * avoid this exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid state. This may happen if another detector has been
- * instantiated or the {@link VoiceInteractionService} hosting this detector has been
- * shut down.
+ * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
- public Intent createEnrollIntent() throws IllegalDetectorStateException {
+ public Intent createEnrollIntent() {
if (DBG) Slog.d(TAG, "createEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL);
@@ -1328,25 +1217,15 @@
* i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
*
* @return An {@link Intent} to start un-enrollment for the given keyphrase.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above.
- * - Thrown if managing they keyphrase isn't supported. Callers should only call this
- * method after a supported state callback on
- * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
- * - Thrown if the detector is in an invalid state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 if managing they keyphrase isn't supported. Callers should only call this method
- * after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
- * avoid this exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid state. This may happen if another detector has been
- * instantiated or the {@link VoiceInteractionService} hosting this detector has been
- * shut down.
+ * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
- public Intent createUnEnrollIntent() throws IllegalDetectorStateException {
+ public Intent createUnEnrollIntent() {
if (DBG) Slog.d(TAG, "createUnEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL);
@@ -1360,25 +1239,15 @@
* i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
*
* @return An {@link Intent} to start re-enrollment for the given keyphrase.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above.
- * - Thrown if managing they keyphrase isn't supported. Callers should only call this
- * method after a supported state callback on
- * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
- * - Thrown if the detector is in an invalid state. This may happen if another detector
- * has been instantiated or the {@link VoiceInteractionService} hosting this detector
- * has been shut down.
- * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
- * 33 if managing they keyphrase isn't supported. Callers should only call this method
- * after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
- * avoid this exception.
- * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
- * detector is in an invalid state. This may happen if another detector has been
- * instantiated or the {@link VoiceInteractionService} hosting this detector has been
- * shut down.
+ * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+ * Callers should only call this method after a supported state callback on
+ * {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+ * @throws IllegalStateException if the detector is in an invalid or error state.
+ * This may happen if another detector has been instantiated or the
+ * {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
- public Intent createReEnrollIntent() throws IllegalDetectorStateException {
+ public Intent createReEnrollIntent() {
if (DBG) Slog.d(TAG, "createReEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL);
@@ -1386,24 +1255,15 @@
}
@GuardedBy("mLock")
- private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action)
- throws IllegalDetectorStateException {
+ private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "getManageIntent called on an invalid detector or error state");
- }
throw new IllegalStateException(
- "getManageIntent called on an invalid detector or error state");
+ "getManageIntent called on an invalid detector or error state");
}
// This method only makes sense if we can actually support a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED
&& mAvailability != STATE_KEYPHRASE_UNENROLLED) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "Managing the given keyphrase is not supported");
- }
throw new UnsupportedOperationException(
"Managing the given keyphrase is not supported");
}
@@ -1487,27 +1347,19 @@
@GuardedBy("mLock")
private int startRecognitionLocked(int recognitionFlags,
- @Nullable byte[] data) throws IllegalDetectorStateException {
+ @Nullable byte[] data) {
if (DBG) {
Slog.d(TAG, "startRecognition("
+ recognitionFlags
+ ", " + Arrays.toString(data) + ")");
}
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "startRecognition called on an invalid detector or error state");
- }
throw new IllegalStateException(
"startRecognition called on an invalid detector or error state");
}
// Check if we can start/stop a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
- if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
- throw new IllegalDetectorStateException(
- "Recognition for the given keyphrase is not supported");
- }
throw new UnsupportedOperationException(
"Recognition for the given keyphrase is not supported");
}
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 93fcec1..0c8fd48 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -23,14 +23,10 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.media.AudioFormat;
-import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
-import android.util.AndroidException;
import java.io.PrintWriter;
@@ -44,23 +40,6 @@
public interface HotwordDetector {
/**
- * Prior to API level 33, API calls of {@link android.service.voice.HotwordDetector} could
- * return both {@link java.lang.IllegalStateException} or
- * {@link java.lang.UnsupportedOperationException} depending on the detector's underlying state.
- * This lead to confusing behavior as the underlying state of the detector can be modified
- * without the knowledge of the caller via system service layer updates.
- *
- * This change ID, when enabled, changes the API calls to only throw checked exception
- * {@link android.service.voice.HotwordDetector.IllegalDetectorStateException} when checking
- * against state information modified by both the caller and the system services.
- *
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
- long HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION = 226355112L;
-
- /**
* Indicates that it is a non-trusted hotword detector.
*
* @hide
@@ -109,26 +88,16 @@
* Calling this again while recognition is active does nothing.
*
* @return {@code true} if the request to start recognition succeeded
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and attempts to start a recognition when the detector is not able based on
- * the state. This can be thrown even if the state has been checked before calling this
- * method because the caller receives updates via an asynchronous callback, and the
- * state of the detector can change concurrently to the caller calling this method.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
- boolean startRecognition() throws IllegalDetectorStateException;
+ boolean startRecognition();
/**
* Stops sandboxed detection recognition.
*
* @return {@code true} if the request to stop recognition succeeded
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and attempts to stop a recognition when the detector is not able based on
- * the state. This can be thrown even if the state has been checked before calling this
- * method because the caller receives updates via an asynchronous callback, and the
- * state of the detector can change concurrently to the caller calling this method.
*/
- boolean stopRecognition() throws IllegalDetectorStateException;
+ boolean stopRecognition();
/**
* Starts hotword recognition on audio coming from an external connected microphone.
@@ -142,16 +111,11 @@
* PersistableBundle does not allow any remotable objects or other contents that can be
* used to communicate with other processes.
* @return {@code true} if the request to start recognition succeeded
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and attempts to start a recognition when the detector is not able based on
- * the state. This can be thrown even if the state has been checked before calling this
- * method because the caller receives updates via an asynchronous callback, and the
- * state of the detector can change concurrently to the caller calling this method.
*/
boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
- @Nullable PersistableBundle options) throws IllegalDetectorStateException;
+ @Nullable PersistableBundle options);
/**
* Set configuration and pass read-only data to sandboxed detection service.
@@ -161,17 +125,10 @@
* communicate with other processes.
* @param sharedMemory The unrestricted data blob to provide to sandboxed detection services.
* Use this to provide model data or other such data to the trusted process.
- * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
- * or above and the detector is not able to perform the operation based on the
- * underlying state. This can be thrown even if the state has been checked before
- * calling this method because the caller receives updates via an asynchronous callback,
- * and the state of the detector can change concurrently to the caller calling this
- * method.
* @throws IllegalStateException if this HotwordDetector wasn't specified to use a
* sandboxed detection service when it was created.
*/
- void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory)
- throws IllegalDetectorStateException;
+ void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
/**
* Invalidates this detector so that any future calls to this result
@@ -298,14 +255,4 @@
*/
void onHotwordDetectionServiceRestarted();
}
-
- /**
- * {@link HotwordDetector} specific exception thrown when the underlying state of the detector
- * is invalid for the given action.
- */
- class IllegalDetectorStateException extends AndroidException {
- IllegalDetectorStateException(String message) {
- super(message);
- }
- }
}
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 767fe37..77900d7 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -86,7 +86,7 @@
@RequiresPermission(RECORD_AUDIO)
@Override
- public boolean startRecognition() throws IllegalDetectorStateException {
+ public boolean startRecognition() {
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
@@ -109,7 +109,7 @@
/** TODO: stopRecognition */
@RequiresPermission(RECORD_AUDIO)
@Override
- public boolean stopRecognition() throws IllegalDetectorStateException {
+ public boolean stopRecognition() {
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 7dc0687..d7bf074 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -84,8 +84,7 @@
* @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
*/
public void updateState(@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory) throws
- HotwordDetector.IllegalDetectorStateException {
+ @Nullable SharedMemory sharedMemory) {
mInitializationDelegate.updateState(options, sharedMemory);
}
@@ -104,7 +103,7 @@
* @see HotwordDetector#startRecognition()
*/
@RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
- public boolean startRecognition() throws HotwordDetector.IllegalDetectorStateException {
+ public boolean startRecognition() {
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
@@ -128,7 +127,7 @@
* @see HotwordDetector#stopRecognition()
*/
@RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
- public boolean stopRecognition() throws HotwordDetector.IllegalDetectorStateException {
+ public boolean stopRecognition() {
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
@@ -236,13 +235,13 @@
}
@Override
- public boolean stopRecognition() throws IllegalDetectorStateException {
+ public boolean stopRecognition() {
throwIfDetectorIsNoLongerActive();
return true;
}
@Override
- public boolean startRecognition() throws IllegalDetectorStateException {
+ public boolean startRecognition() {
throwIfDetectorIsNoLongerActive();
return true;
}
@@ -251,7 +250,7 @@
public final boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
- @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+ @Nullable PersistableBundle options) {
//No-op, not supported by VisualQueryDetector as it should be trusted.
return false;
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 3134dcd..1148fe3 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -78,23 +78,10 @@
* information see {@link #checkRecognitionSupport}, {@link #startListening} and
* {@link RecognizerIntent}.
*
- * Progress can be monitord by calling {@link #setModelDownloadListener} before a trigger.
+ * Progress updates can be received via {@link #IModelDownloadListener}.
*/
- void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);
-
- /**
- * Sets listener to received download progress updates. Clients still have to call
- * {@link #triggerModelDownload} to trigger a model download.
- */
- void setModelDownloadListener(
+ void triggerModelDownload(
in Intent recognizerIntent,
in AttributionSource attributionSource,
in IModelDownloadListener listener);
-
- /**
- * Clears the listener for model download events attached to a recognitionIntent if any.
- */
- void clearModelDownloadListener(
- in Intent recognizerIntent,
- in AttributionSource attributionSource);
}
diff --git a/core/java/android/speech/ModelDownloadListener.java b/core/java/android/speech/ModelDownloadListener.java
index 6c24399..a58ec90c 100644
--- a/core/java/android/speech/ModelDownloadListener.java
+++ b/core/java/android/speech/ModelDownloadListener.java
@@ -22,20 +22,27 @@
*/
public interface ModelDownloadListener {
/**
- * Called by {@link RecognitionService} when there's an update on the download progress.
+ * Called by {@link RecognitionService} only if the download has started after the request.
*
- * <p>RecognitionService will call this zero or more times during the download.</p>
+ * <p> The number of calls to this method varies depending of the {@link RecognitionService}
+ * implementation. If the download finished quickly enough, {@link #onSuccess()} may be called
+ * directly. In other cases, this method may be called any number of times during the download.
+ *
+ * @param completedPercent the percentage of download that is completed
*/
void onProgress(int completedPercent);
/**
- * Called when {@link RecognitionService} completed the download and it can now be used to
- * satisfy recognition requests.
+ * This method is called:
+ * <li> if the model is already available;
+ * <li> if the {@link RecognitionService} has started and completed the download.
+ *
+ * <p> Once this method is called, the model can be safely used to satisfy recognition requests.
*/
void onSuccess();
/**
- * Called when {@link RecognitionService} scheduled the download but won't satisfy it
+ * Called when {@link RecognitionService} scheduled the download, but won't satisfy it
* immediately. There will be no further updates on this listener.
*/
void onScheduled();
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 4ecec8f..9656f36 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -36,9 +36,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -93,10 +91,6 @@
private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;
- private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 7;
-
- private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 8;
-
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -120,21 +114,11 @@
checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
break;
case MSG_TRIGGER_MODEL_DOWNLOAD:
- Pair<Intent, AttributionSource> params =
- (Pair<Intent, AttributionSource>) msg.obj;
- dispatchTriggerModelDownload(params.first, params.second);
- break;
- case MSG_SET_MODEL_DOWNLOAD_LISTENER:
- ModelDownloadListenerArgs dListenerArgs = (ModelDownloadListenerArgs) msg.obj;
- dispatchSetModelDownloadListener(
- dListenerArgs.mIntent,
- dListenerArgs.mListener,
- dListenerArgs.mAttributionSource);
- break;
- case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
- Pair<Intent, AttributionSource> clearDlPair =
- (Pair<Intent, AttributionSource>) msg.obj;
- dispatchClearModelDownloadListener(clearDlPair.first, clearDlPair.second);
+ ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
+ dispatchTriggerModelDownload(
+ modelDownloadArgs.mIntent,
+ modelDownloadArgs.mAttributionSource,
+ modelDownloadArgs.mListener);
break;
}
}
@@ -239,59 +223,52 @@
private void dispatchTriggerModelDownload(
Intent intent,
- AttributionSource attributionSource) {
- RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
- }
-
- private void dispatchSetModelDownloadListener(
- Intent intent,
- IModelDownloadListener listener,
- AttributionSource attributionSource) {
- RecognitionService.this.setModelDownloadListener(
- intent,
- attributionSource,
- new ModelDownloadListener() {
- @Override
- public void onProgress(int completedPercent) {
- try {
- listener.onProgress(completedPercent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ AttributionSource attributionSource,
+ IModelDownloadListener listener) {
+ if (listener == null) {
+ RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
+ } else {
+ RecognitionService.this.onTriggerModelDownload(
+ intent,
+ attributionSource,
+ new ModelDownloadListener() {
+ @Override
+ public void onProgress(int completedPercent) {
+ try {
+ listener.onProgress(completedPercent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- }
- @Override
- public void onSuccess() {
- try {
- listener.onSuccess();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ @Override
+ public void onSuccess() {
+ try {
+ listener.onSuccess();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- }
- @Override
- public void onScheduled() {
- try {
- listener.onScheduled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ @Override
+ public void onScheduled() {
+ try {
+ listener.onScheduled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- }
- @Override
- public void onError(int error) {
- try {
- listener.onError(error);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ @Override
+ public void onError(int error) {
+ try {
+ listener.onError(error);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- }
- });
- }
-
- private void dispatchClearModelDownloadListener(
- Intent intent, AttributionSource attributionSource) {
- RecognitionService.this.clearModelDownloadListener(intent, attributionSource);
+ });
+ }
}
private static class StartListeningArgs {
@@ -323,17 +300,18 @@
}
}
- private static class ModelDownloadListenerArgs {
+ private static class ModelDownloadArgs {
final Intent mIntent;
- final IModelDownloadListener mListener;
final AttributionSource mAttributionSource;
+ @Nullable final IModelDownloadListener mListener;
- private ModelDownloadListenerArgs(Intent intent,
- IModelDownloadListener listener,
- AttributionSource attributionSource) {
- mIntent = intent;
+ private ModelDownloadArgs(
+ Intent intent,
+ AttributionSource attributionSource,
+ @Nullable IModelDownloadListener listener) {
+ this.mIntent = intent;
+ this.mAttributionSource = attributionSource;
this.mListener = listener;
- mAttributionSource = attributionSource;
}
}
@@ -443,38 +421,39 @@
}
/**
- * Sets a {@link ModelDownloadListener} to receive progress updates after
- * {@link #onTriggerModelDownload} calls.
+ * Requests the download of the recognizer support for {@code recognizerIntent}.
*
- * @param recognizerIntent the request to monitor model download progress for.
- * @param modelDownloadListener the listener to keep updated.
+ * <p> Provides the calling {@link AttributionSource} to the service implementation so that
+ * permissions and bandwidth could be correctly blamed.
+ *
+ * <p> Client will receive the progress updates via the given {@link ModelDownloadListener}:
+ *
+ * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+ * called directly. The model can be safely used afterwards.
+ *
+ * <li> If the {@link RecognitionService} has started the download,
+ * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+ * number of times until the download is complete.
+ * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+ * The model can be safely used afterwards.
+ *
+ * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+ * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+ * There will be no further updates on this listener.
+ *
+ * <li> If the request fails at any time due to a network or scheduling error,
+ * {@link ModelDownloadListener#onError(int)} will be called.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}.
+ * @param attributionSource the attribution source of the caller.
+ * @param listener on which to receive updates about the model download request.
*/
- public void setModelDownloadListener(
+ public void onTriggerModelDownload(
@NonNull Intent recognizerIntent,
@NonNull AttributionSource attributionSource,
- @NonNull ModelDownloadListener modelDownloadListener) {
- if (DBG) {
- Log.i(TAG, TextUtils.formatSimple(
- "#setModelDownloadListener [%s] [%s]",
- recognizerIntent,
- modelDownloadListener));
- }
- modelDownloadListener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
- }
-
- /**
- * Clears the {@link ModelDownloadListener} set to receive progress updates for the given
- * {@code recognizerIntent}, if any.
- *
- * @param recognizerIntent the request to monitor model download progress for.
- */
- public void clearModelDownloadListener(
- @NonNull Intent recognizerIntent,
- @NonNull AttributionSource attributionSource) {
- if (DBG) {
- Log.i(TAG, TextUtils.formatSimple(
- "#clearModelDownloadListener [%s]", recognizerIntent));
- }
+ @NonNull ModelDownloadListener listener) {
+ listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
}
@Override
@@ -815,41 +794,18 @@
@Override
public void triggerModelDownload(
- Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
+ Intent recognizerIntent,
+ @NonNull AttributionSource attributionSource,
+ IModelDownloadListener listener) {
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(
service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
- Pair.create(recognizerIntent, attributionSource)));
- }
- }
-
- @Override
- public void setModelDownloadListener(
- Intent recognizerIntent,
- AttributionSource attributionSource,
- IModelDownloadListener listener) throws RemoteException {
- final RecognitionService service = mServiceRef.get();
- if (service != null) {
- service.mHandler.sendMessage(
- Message.obtain(service.mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
- new ModelDownloadListenerArgs(
+ new ModelDownloadArgs(
recognizerIntent,
- listener,
- attributionSource)));
- }
- }
-
- @Override
- public void clearModelDownloadListener(
- Intent recognizerIntent,
- AttributionSource attributionSource) throws RemoteException {
- final RecognitionService service = mServiceRef.get();
- if (service != null) {
- service.mHandler.sendMessage(
- Message.obtain(service.mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER,
- Pair.create(recognizerIntent, attributionSource)));
+ attributionSource,
+ listener)));
}
}
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 76eb09e..dacb25c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -297,8 +297,6 @@
private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
- private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 8;
- private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 9;
/** The actual RecognitionService endpoint */
private IRecognitionService mService;
@@ -341,19 +339,13 @@
args.mIntent, args.mCallbackExecutor, args.mCallback);
break;
case MSG_TRIGGER_MODEL_DOWNLOAD:
- handleTriggerModelDownload((Intent) msg.obj);
- break;
- case MSG_SET_MODEL_DOWNLOAD_LISTENER:
ModelDownloadListenerArgs modelDownloadListenerArgs =
(ModelDownloadListenerArgs) msg.obj;
- handleSetModelDownloadListener(
+ handleTriggerModelDownload(
modelDownloadListenerArgs.mIntent,
modelDownloadListenerArgs.mExecutor,
modelDownloadListenerArgs.mModelDownloadListener);
break;
- case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
- handleClearModelDownloadListener((Intent) msg.obj);
- break;
}
}
};
@@ -657,17 +649,13 @@
* user interaction to approve the download. Callers can verify the status of the request via
* {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
*
- * <p>Listeners set via
- * {@link #setModelDownloadListener(Intent, Executor, ModelDownloadListener)} will receive
- * updates about this download request.</p>
- *
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* may also contain optional extras, see {@link RecognizerIntent}.
*/
public void triggerModelDownload(@NonNull Intent recognizerIntent) {
Objects.requireNonNull(recognizerIntent, "intent must not be null");
if (DBG) {
- Slog.i(TAG, "#triggerModelDownload called");
+ Slog.i(TAG, "#triggerModelDownload without a listener called");
if (mService == null) {
Slog.i(TAG, "Connection is not established yet");
}
@@ -676,23 +664,47 @@
// First time connection: first establish a connection, then dispatch.
connectToSystemService();
}
- putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
+ putMessage(Message.obtain(
+ mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
+ new ModelDownloadListenerArgs(recognizerIntent, null, null)));
}
/**
- * Sets a listener to model download updates. Clients will have to call this method before
- * {@link #triggerModelDownload(Intent)}.
+ * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
+ * user interaction to approve the download. Callers can verify the status of the request via
+ * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
*
- * @param recognizerIntent the request to monitor support for.
+ * <p> The updates about the model download request are received via the given
+ * {@link ModelDownloadListener}:
+ *
+ * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+ * called directly. The model can be safely used afterwards.
+ *
+ * <li> If the {@link RecognitionService} has started the download,
+ * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+ * number of times until the download is complete.
+ * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+ * The model can be safely used afterwards.
+ *
+ * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+ * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+ * There will be no further updates on this listener.
+ *
+ * <li> If the request fails at any time due to a network or scheduling error,
+ * {@link ModelDownloadListener#onError(int)} will be called.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}.
+ * @param executor for dispatching listener callbacks
* @param listener on which to receive updates about the model download request.
*/
- public void setModelDownloadListener(
+ public void triggerModelDownload(
@NonNull Intent recognizerIntent,
@NonNull @CallbackExecutor Executor executor,
@NonNull ModelDownloadListener listener) {
Objects.requireNonNull(recognizerIntent, "intent must not be null");
if (DBG) {
- Slog.i(TAG, "#setModelDownloadListener called");
+ Slog.i(TAG, "#triggerModelDownload with a listener called");
if (mService == null) {
Slog.i(TAG, "Connection is not established yet");
}
@@ -702,32 +714,11 @@
connectToSystemService();
}
putMessage(Message.obtain(
- mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
+ mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
}
/**
- * Clears the listener for model download updates if any.
- *
- * @param recognizerIntent the request to monitor support for.
- */
- public void clearModelDownloadListener(@NonNull Intent recognizerIntent) {
- Objects.requireNonNull(recognizerIntent, "intent must not be null");
- if (DBG) {
- Slog.i(TAG, "#clearModelDownloadListener called");
- if (mService == null) {
- Slog.i(TAG, "Connection is not established yet");
- }
- }
- if (mService == null) {
- // First time connection: first establish a connection, then dispatch.
- connectToSystemService();
- }
- putMessage(Message.obtain(
- mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER, recognizerIntent));
- }
-
- /**
* Sets a temporary component to power on-device speech recognizer.
*
* <p>This is only expected to be called in tests, system would reject calls from client apps.
@@ -836,51 +827,36 @@
}
}
- private void handleTriggerModelDownload(Intent recognizerIntent) {
- if (!maybeInitializeManagerService()) {
- return;
- }
- try {
- mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
- } catch (final RemoteException e) {
- Log.e(TAG, "downloadModel() failed", e);
- mListener.onError(ERROR_CLIENT);
- }
- }
-
- private void handleSetModelDownloadListener(
+ private void handleTriggerModelDownload(
Intent recognizerIntent,
- Executor callbackExecutor,
+ @Nullable Executor callbackExecutor,
@Nullable ModelDownloadListener modelDownloadListener) {
if (!maybeInitializeManagerService()) {
return;
}
- try {
- InternalModelDownloadListener listener =
- modelDownloadListener == null
- ? null
- : new InternalModelDownloadListener(
- callbackExecutor,
- modelDownloadListener);
- mService.setModelDownloadListener(
- recognizerIntent, mContext.getAttributionSource(), listener);
- if (DBG) Log.d(TAG, "setModelDownloadListener()");
- } catch (final RemoteException e) {
- Log.e(TAG, "setModelDownloadListener() failed", e);
- callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
- }
- }
- private void handleClearModelDownloadListener(Intent recognizerIntent) {
- if (!maybeInitializeManagerService()) {
- return;
+ // Trigger model download without a listener.
+ if (modelDownloadListener == null) {
+ try {
+ mService.triggerModelDownload(
+ recognizerIntent, mContext.getAttributionSource(), null);
+ if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "triggerModelDownload() without a listener failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
}
- try {
- mService.clearModelDownloadListener(
- recognizerIntent, mContext.getAttributionSource());
- if (DBG) Log.d(TAG, "clearModelDownloadListener()");
- } catch (final RemoteException e) {
- Log.e(TAG, "clearModelDownloadListener() failed", e);
+ // Trigger model download with a listener.
+ else {
+ try {
+ mService.triggerModelDownload(
+ recognizerIntent, mContext.getAttributionSource(),
+ new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
+ if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "triggerModelDownload() with a listener failed", e);
+ callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
+ }
}
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
new file mode 100644
index 0000000..9f11e31
--- /dev/null
+++ b/core/java/android/text/TextFlags.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/**
+ * Flags in the "text" namespace.
+ *
+ * @hide
+ */
+public final class TextFlags {
+
+ /**
+ * The name space of the "text" feature.
+ *
+ * This needs to move to DeviceConfig constant.
+ */
+ public static final String NAMESPACE = "text";
+
+ /**
+ * Whether we use the new design of context menu.
+ */
+ public static final String ENABLE_NEW_CONTEXT_MENU =
+ "TextEditing__enable_new_context_menu";
+
+ /**
+ * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}.
+ */
+ public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu";
+
+ /**
+ * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}.
+ */
+ public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = false;
+
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index b4a1e8c..c43864d 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -362,6 +362,15 @@
return true;
}
+ } else if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
+ // If user is in the process of composing with a dead key, and
+ // presses Escape, cancel it. We need special handling because
+ // the Escape key will not produce a Unicode character
+ if (activeStart == selStart && activeEnd == selEnd) {
+ Selection.setSelection(content, selEnd);
+ content.removeSpan(TextKeyListener.ACTIVE);
+ return true;
+ }
}
return super.onKeyDown(view, content, keyCode, event);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2ae882c..6201b3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8c4e90c..c92b1b8 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -195,7 +195,7 @@
private boolean mDebugPrintNextFrameTimeDelta;
private int mFPSDivisor = 1;
- private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+ private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
new DisplayEventReceiver.VsyncEventData();
private final FrameData mFrameData = new FrameData();
@@ -857,7 +857,7 @@
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastFrameIntervalNanos = frameIntervalNanos;
- mLastVsyncEventData = vsyncEventData;
+ mLastVsyncEventData.copyFrom(vsyncEventData);
}
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
@@ -1247,7 +1247,7 @@
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
- private VsyncEventData mLastVsyncEventData = new VsyncEventData();
+ private final VsyncEventData mLastVsyncEventData = new VsyncEventData();
FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
@@ -1287,7 +1287,7 @@
mTimestampNanos = timestampNanos;
mFrame = frame;
- mLastVsyncEventData = vsyncEventData;
+ mLastVsyncEventData.copyFrom(vsyncEventData);
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index b4675e0..54db34e 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -146,7 +146,12 @@
mMessageQueue = null;
}
- static final class VsyncEventData {
+ /**
+ * Class to capture all inputs required for syncing events data.
+ *
+ * @hide
+ */
+ public static final class VsyncEventData {
// The amount of frame timeline choices.
// Must be in sync with VsyncEventData::kFrameTimelinesLength in
// frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime
@@ -164,6 +169,12 @@
this.deadline = deadline;
}
+ void copyFrom(FrameTimeline other) {
+ vsyncId = other.vsyncId;
+ expectedPresentationTime = other.expectedPresentationTime;
+ deadline = other.deadline;
+ }
+
// The frame timeline vsync id, used to correlate a frame
// produced by HWUI with the timeline data stored in Surface Flinger.
public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
@@ -203,6 +214,14 @@
this.frameInterval = frameInterval;
}
+ void copyFrom(VsyncEventData other) {
+ preferredFrameTimelineIndex = other.preferredFrameTimelineIndex;
+ frameInterval = other.frameInterval;
+ for (int i = 0; i < frameTimelines.length; i++) {
+ frameTimelines[i].copyFrom(other.frameTimelines[i]);
+ }
+ }
+
public FrameTimeline preferredFrameTimeline() {
return frameTimelines[preferredFrameTimelineIndex];
}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a47783c..6b60442 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -205,6 +205,16 @@
}
/**
+ * Notify HandwritingInitiator that a delegate view (see {@link View#isHandwritingDelegate})
+ * gained focus.
+ */
+ public void onDelegateViewFocused(@NonNull View view) {
+ if (view == getConnectedView()) {
+ tryAcceptStylusHandwritingDelegation(view);
+ }
+ }
+
+ /**
* Notify HandwritingInitiator that a new InputConnection is created.
* The caller of this method should guarantee that each onInputConnectionCreated call
* is paired with a onInputConnectionClosed call.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 5810642..48b112d 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -110,16 +110,6 @@
int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
int lastSyncSeqId);
- /*
- * Notify the window manager that an application is relaunching and
- * windows should be prepared for replacement.
- *
- * @param appToken The application
- * @param childrenOnly Whether to only prepare child windows for replacement
- * (for example when main windows are being reused via preservation).
- */
- oneway void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly);
-
/**
* Called by a client to report that it ran out of graphics memory.
*/
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 720f569..9225cd9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -29,6 +29,7 @@
import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.hardware.lights.LightsManager;
import android.icu.util.ULocale;
import android.os.Build;
@@ -742,7 +743,7 @@
*/
@Nullable
public static InputDevice getDevice(int id) {
- return InputManager.getInstance().getInputDevice(id);
+ return InputManagerGlobal.getInstance().getInputDevice(id);
}
/**
@@ -750,7 +751,7 @@
* @return The input device ids.
*/
public static int[] getDeviceIds() {
- return InputManager.getInstance().getInputDeviceIds();
+ return InputManagerGlobal.getInstance().getInputDeviceIds();
}
/**
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 7d452f9..27af300 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -49,15 +49,17 @@
// Pointer to the native object.
private final long mPtr;
- private final Context mContext;
+ // Device-specific override to enable/disable motion prediction.
+ private final boolean mIsPredictionEnabled;
/**
* Create a new MotionPredictor for the provided {@link Context}.
* @param context The context for the predictions
*/
public MotionPredictor(@NonNull Context context) {
- mContext = context;
- final int offsetNanos = mContext.getResources().getInteger(
+ mIsPredictionEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableMotionPrediction);
+ final int offsetNanos = context.getResources().getInteger(
com.android.internal.R.integer.config_motionPredictionOffsetNanos);
mPtr = nativeInitialize(offsetNanos);
RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
@@ -73,7 +75,7 @@
* @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
*/
public void record(@NonNull MotionEvent event) {
- if (!isPredictionEnabled()) {
+ if (!mIsPredictionEnabled) {
return;
}
nativeRecord(mPtr, event);
@@ -94,21 +96,12 @@
*/
@Nullable
public MotionEvent predict(long predictionTimeNanos) {
- if (!isPredictionEnabled()) {
+ if (!mIsPredictionEnabled) {
return null;
}
return nativePredict(mPtr, predictionTimeNanos);
}
- private boolean isPredictionEnabled() {
- // Device-specific override
- if (!mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableMotionPrediction)) {
- return false;
- }
- return true;
- }
-
/**
* Check whether a device supports motion predictions for a given source type.
*
@@ -120,7 +113,7 @@
* @see MotionEvent#getSource
*/
public boolean isPredictionAvailable(int deviceId, int source) {
- return isPredictionEnabled() && nativeIsPredictionAvailable(mPtr, deviceId, source);
+ return mIsPredictionEnabled && nativeIsPredictionAvailable(mPtr, deviceId, source);
}
private static native long nativeInitialize(int offsetNanos);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 800fc97..aec3487 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8324,6 +8324,13 @@
onFocusLost();
} else if (hasWindowFocus()) {
notifyFocusChangeToImeFocusController(true /* hasFocus */);
+
+ if (mIsHandwritingDelegate) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getHandwritingInitiator().onDelegateViewFocused(this);
+ }
+ }
}
invalidate(true);
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c96d298..d80819f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -28,6 +28,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
@@ -1188,7 +1189,7 @@
}
private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
- InputDevice device = InputManager.getInstance().getInputDevice(id);
+ InputDevice device = InputManagerGlobal.getInstance().getInputDevice(id);
return device != null && device.getMotionRange(axis, source) != null;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24dcb69..fb25e7a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11626,7 +11626,8 @@
mNumPausedForSync++;
mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
- mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, 1000);
+ mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT,
+ 1000 * Build.HW_TIMEOUT_MULTIPLIER);
return mActiveSurfaceSyncGroup;
};
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 35ed88f..cc846e3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -863,10 +863,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
@@ -899,8 +897,6 @@
* android:value="false"/>
* </application>
* </pre>
- *
- * @hide
*/
// TODO(b/263984287): Make this public API.
String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
@@ -937,10 +933,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
@@ -976,10 +970,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -1023,10 +1015,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
@@ -1073,17 +1063,28 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the app should be excluded from the
- * compatibility override for orientation set by the device manufacturer.
+ * compatibility override for orientation set by the device manufacturer. When the orientation
+ * override is applied it can:
+ * <ul>
+ * <li>Replace the specific orientation requested by the app with another selected by the
+ device manufacturer, e.g. replace undefined requested by the app with portrait.
+ * <li>Always use an orientation selected by the device manufacturer.
+ * <li>Do one of the above but only when camera connection is open.
+ * </ul>
+ *
+ * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+ * (which is used to avoid orientation loops caused by the incorrect use of {@link
+ * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
+ * orientation selected by the device manufacturer rather than ignoring one of orientation
+ * requests coming from the app while respecting the previous one.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
@@ -1099,10 +1100,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
@@ -1144,10 +1143,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/263984287): Make this public API.
+ // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
@@ -2815,7 +2812,7 @@
*
* @hide
*/
- public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
+ public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 1 << 1;
/**
* By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
@@ -2826,7 +2823,7 @@
*
* @hide
*/
- public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+ public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 1 << 2;
/**
* When set {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows will stay visible, even if
@@ -2835,7 +2832,7 @@
* @hide
*/
@RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY)
- public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008;
+ public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 1 << 3;
/** In a multiuser system if this flag is set and the owner is a system process then this
* window will appear on all user screens. This overrides the default behavior of window
@@ -2845,7 +2842,7 @@
* {@hide} */
@SystemApi
@RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
- public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+ public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 1 << 4;
/**
* Flag to allow this window to have unrestricted gesture exclusion.
@@ -2853,7 +2850,7 @@
* @see View#setSystemGestureExclusionRects(List)
* @hide
*/
- public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+ public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 1 << 5;
/**
* Never animate position changes of the window.
@@ -2862,20 +2859,20 @@
* {@hide}
*/
@UnsupportedAppUsage
- public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+ public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
/** Window flag: special flag to limit the size of the window to be
* original size ([320x480] x density). Used to create window for applications
* running under compatibility mode.
*
* {@hide} */
- public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
+ public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
* {@hide} */
- public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+ public static final int PRIVATE_FLAG_SYSTEM_ERROR = 1 << 8;
/**
* Flag to indicate that the view hierarchy of the window can only be measured when
@@ -2884,14 +2881,14 @@
* views. This reduces the chances to perform measure.
* {@hide}
*/
- public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+ public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 1 << 9;
/**
* Flag that prevents the wallpaper behind the current window from receiving touch events.
*
* {@hide}
*/
- public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+ public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
/**
* Flag to force the status bar window to be visible all the time. If the bar is hidden when
@@ -2900,7 +2897,7 @@
*
* {@hide}
*/
- public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
+ public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
/**
* Flag to indicate that the window frame should be the requested frame adding the display
@@ -2910,7 +2907,7 @@
*
* {@hide}
*/
- public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+ public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 1 << 12;
/**
* Flag that will make window ignore app visibility and instead depend purely on the decor
@@ -2918,39 +2915,28 @@
* drawing after it launches an app.
* @hide
*/
- public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
-
- /**
- * Flag to indicate that this window is not expected to be replaced across
- * configuration change triggered activity relaunches. In general the WindowManager
- * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
- * until the replacement is ready to show in order to prevent visual glitch. However
- * some windows, such as PopupWindows expect to be cleared across configuration change,
- * and thus should hint to the WindowManager that it should not wait for a replacement.
- * @hide
- */
- public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
+ public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 1 << 13;
/**
* Flag to indicate that this child window should always be laid-out in the parent
* frame regardless of the current windowing mode configuration.
* @hide
*/
- public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+ public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 1 << 14;
/**
* Flag to indicate that this window is always drawing the status bar background, no matter
* what the other flags are.
* @hide
*/
- public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 0x00020000;
+ public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 1 << 15;
/**
* Flag to indicate that this window needs Sustained Performance Mode if
* the device supports it.
* @hide
*/
- public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000;
+ public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16;
/**
* Flag to indicate that any window added by an application process that is of type
@@ -2961,7 +2947,7 @@
*/
@SystemApi
@RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
- public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+ public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 1 << 19;
/**
* Indicates that this window is the rounded corners overlay present on some
@@ -2969,7 +2955,7 @@
* screen magnification, and mirroring.
* @hide
*/
- public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
+ public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 1 << 20;
/**
* Flag to indicate that this window will be excluded while computing the magnifiable region
@@ -2983,7 +2969,7 @@
* </p><p>
* @hide
*/
- public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000;
+ public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 1 << 21;
/**
* Flag to prevent the window from being magnified by the accessibility magnifier.
@@ -2991,7 +2977,7 @@
* TODO(b/190623172): This is a temporary solution and need to find out another way instead.
* @hide
*/
- public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000;
+ public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
/**
* Flag to indicate that the status bar window is in a state such that it forces showing
@@ -3000,54 +2986,54 @@
* It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
* @hide
*/
- public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 0x00800000;
+ public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
/**
* Flag to indicate that the window is color space agnostic, and the color can be
* interpreted to any color space.
* @hide
*/
- public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000;
+ public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
/**
* Flag to request creation of a BLAST (Buffer as LayerState) Layer.
* If not specified the client will receive a BufferQueue layer.
* @hide
*/
- public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
+ public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25;
/**
* Flag to indicate that the window is controlling the appearance of system bars. So we
* don't need to adjust it by reading its system UI flags for compatibility.
* @hide
*/
- public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000;
+ public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 1 << 26;
/**
* Flag to indicate that the window is controlling the behavior of system bars. So we don't
* need to adjust it by reading its window flags or system UI flags for compatibility.
* @hide
*/
- public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000;
+ public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 1 << 27;
/**
* Flag to indicate that the window is controlling how it fits window insets on its own.
* So we don't need to adjust its attributes for fitting window insets.
* @hide
*/
- public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
+ public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 1 << 28;
/**
* Flag to indicate that the window is a trusted overlay.
* @hide
*/
- public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+ public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 1 << 29;
/**
* Flag to indicate that the parent frame of a window should be inset by IME.
* @hide
*/
- public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
+ public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 1 << 30;
/**
* Flag to indicate that we want to intercept and handle global drag and drop for all users.
@@ -3062,7 +3048,7 @@
* @hide
*/
@RequiresPermission(permission.MANAGE_ACTIVITY_TASKS)
- public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000;
+ public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 1 << 31;
/**
* An internal annotation for flags that can be specified to {@link #softInputMode}.
@@ -3093,7 +3079,6 @@
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
- PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
@@ -3169,10 +3154,6 @@
equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
name = "FORCE_DECOR_VIEW_VISIBILITY"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
- equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
- name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b157ea0..b77b7a6 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -415,10 +415,6 @@
}
@Override
- public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
- }
-
- @Override
public boolean outOfMemory(android.view.IWindow window) {
return false;
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9f9a781..dce5432 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -74,6 +74,7 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
+import android.text.TextFlags;
import android.text.TextUtils;
import android.text.method.InsertModeTransformationMethod;
import android.text.method.KeyListener;
@@ -169,9 +170,6 @@
private static final String TAG = "Editor";
private static final boolean DEBUG_UNDO = false;
- // TODO(nona): Make this configurable.
- private static final boolean FLAG_USE_NEW_CONTEXT_MENU = false;
-
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -470,6 +468,7 @@
private static final int LINE_CHANGE_SLOP_MIN_DP = 8;
private int mLineChangeSlopMax;
private int mLineChangeSlopMin;
+ private boolean mUseNewContextMenu;
private final AccessibilitySmartActions mA11ySmartActions;
private InsertModeController mInsertModeController;
@@ -500,6 +499,9 @@
mLineSlopRatio = AppGlobals.getFloatCoreSetting(
WidgetFlags.KEY_LINE_SLOP_RATIO,
WidgetFlags.LINE_SLOP_RATIO_DEFAULT);
+ mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
if (TextView.DEBUG_CURSOR) {
logCursor("Editor", "Cursor drag from anywhere is %s.",
mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
@@ -3171,7 +3173,7 @@
final int menuItemOrderSelectAll;
final int menuItemOrderShare;
final int menuItemOrderAutofill;
- if (FLAG_USE_NEW_CONTEXT_MENU) {
+ if (mUseNewContextMenu) {
menuItemOrderPasteAsPlainText = 7;
menuItemOrderSelectAll = 8;
menuItemOrderShare = 9;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index c1ec168..d54addb 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -19,7 +19,6 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1644,8 +1643,7 @@
p.width = mLastWidth = mWidth;
}
- p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
- | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+ p.privateFlags = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1600a16..fd80981 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -462,12 +462,12 @@
private static final int CHANGE_WATCHER_PRIORITY = 100;
/**
- * The span priority of the {@link TransformationMethod} that is set on the text. It must be
+ * The span priority of the {@link OffsetMapping} that is set on the text. It must be
* higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
* updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
* by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
*/
- private static final int TRANSFORMATION_SPAN_PRIORITY = 200;
+ private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
// New state used to change background based on whether this TextView is multiline.
private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
@@ -7033,9 +7033,9 @@
}
final int textLength = text.length();
+ final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
- if (text instanceof Spannable && (!mAllowTransformationLengthChange
- || text instanceof OffsetMapping)) {
+ if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
Spannable sp = (Spannable) text;
// Remove any ChangeWatchers that might have come from other TextViews.
@@ -7053,8 +7053,9 @@
if (mEditor != null) mEditor.addSpanWatchers(sp);
if (mTransformation != null) {
+ final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
- | (TRANSFORMATION_SPAN_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+ | (priority << Spanned.SPAN_PRIORITY_SHIFT));
}
if (mMovement != null) {
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 071c20f..52e17ca 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -34,6 +34,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
@@ -223,6 +224,11 @@
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
+ if (background == null) {
+ Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
+ + mTitle);
+ return;
+ }
// TODO: Support this on HardwareBuffer
final Canvas c = background.lockCanvas();
drawBackgroundAndBars(c, frame);
@@ -410,6 +416,7 @@
layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
layoutParams.setTitle(title);
+ layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
return layoutParams;
}
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 0672d63..7f99fb7 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -21,6 +21,7 @@
import android.annotation.UiThread;
import android.os.Binder;
import android.os.BinderProxy;
+import android.os.Build;
import android.os.Debug;
import android.os.Handler;
import android.os.HandlerThread;
@@ -62,7 +63,7 @@
private static final int MAX_COUNT = 100;
private static final AtomicInteger sCounter = new AtomicInteger(0);
- private static final int TRANSACTION_READY_TIMEOUT = 1000;
+ private static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
private static Supplier<Transaction> sTransactionFactory = Transaction::new;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b8bd703..4c482460 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -24,6 +24,7 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -58,6 +59,7 @@
* @hide
*/
public final class TransitionInfo implements Parcelable {
+ private static final String TAG = "TransitionInfo";
/**
* Modes are only a sub-set of all the transit-types since they are per-container
@@ -184,9 +186,7 @@
private final @TransitionType int mType;
private final @TransitionFlags int mFlags;
private final ArrayList<Change> mChanges = new ArrayList<>();
-
- private SurfaceControl mRootLeash;
- private final Point mRootOffset = new Point();
+ private final ArrayList<Root> mRoots = new ArrayList<>();
private AnimationOptions mOptions;
@@ -200,10 +200,7 @@
mType = in.readInt();
mFlags = in.readInt();
in.readTypedList(mChanges, Change.CREATOR);
- mRootLeash = new SurfaceControl();
- mRootLeash.readFromParcel(in);
- mRootLeash.setUnreleasedWarningCallSite("TransitionInfo");
- mRootOffset.readFromParcel(in);
+ in.readTypedList(mRoots, Root.CREATOR);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
}
@@ -213,8 +210,7 @@
dest.writeInt(mType);
dest.writeInt(mFlags);
dest.writeTypedList(mChanges);
- mRootLeash.writeToParcel(dest, flags);
- mRootOffset.writeToParcel(dest, flags);
+ dest.writeTypedList(mRoots, flags);
dest.writeTypedObject(mOptions, flags);
}
@@ -238,10 +234,15 @@
return 0;
}
- /** @see #getRootLeash() */
- public void setRootLeash(@NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
- mRootLeash = leash;
- mRootOffset.set(offsetLeft, offsetTop);
+ /** @see #getRoot */
+ public void addRootLeash(int displayId, @NonNull SurfaceControl leash,
+ int offsetLeft, int offsetTop) {
+ mRoots.add(new Root(displayId, leash, offsetLeft, offsetTop));
+ }
+
+ /** @see #getRoot */
+ public void addRoot(Root other) {
+ mRoots.add(other);
}
public void setAnimationOptions(AnimationOptions options) {
@@ -257,23 +258,52 @@
}
/**
+ * @return The number of animation roots. Most transitions should have 1, but there may be more
+ * in some cases (such as a transition spanning multiple displays).
+ */
+ public int getRootCount() {
+ return mRoots.size();
+ }
+
+ /**
+ * @return the transition-root at a specific index.
+ */
+ @NonNull
+ public Root getRoot(int idx) {
+ return mRoots.get(idx);
+ }
+
+ /**
+ * @return the index of the transition-root associated with `displayId` or -1 if not found.
+ */
+ public int findRootIndex(int displayId) {
+ for (int i = 0; i < mRoots.size(); ++i) {
+ if (mRoots.get(i).mDisplayId == displayId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
* @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
* participants to animate within. This will generally be placed at the highest-z-order
* shared ancestor of all participants. While this is non-null, it's possible for the rootleash
* to be invalid if the transition is a no-op.
+ *
+ * @deprecated Use {@link #getRoot} instead. This call assumes there is only one root.
*/
+ @Deprecated
@NonNull
public SurfaceControl getRootLeash() {
- if (mRootLeash == null) {
- throw new IllegalStateException("Trying to get a leash which wasn't set");
+ if (mRoots.isEmpty()) {
+ throw new IllegalStateException("Trying to get a root leash from a no-op transition.");
}
- return mRootLeash;
- }
-
- /** @return the offset (relative to the screen) of the root leash. */
- @NonNull
- public Point getRootOffset() {
- return mRootOffset;
+ if (mRoots.size() > 1) {
+ android.util.Log.e(TAG, "Assuming one animation root when there are more.",
+ new Throwable());
+ }
+ return mRoots.get(0).mLeash;
}
public AnimationOptions getAnimationOptions() {
@@ -320,8 +350,15 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
- + " ro=" + mRootOffset + " c=[");
+ sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
+ .append(Integer.toHexString(mFlags)).append(" r=[");
+ for (int i = 0; i < mRoots.size(); ++i) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
+ }
+ sb.append("] c=[");
for (int i = 0; i < mChanges.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -448,8 +485,8 @@
c.mSnapshot = null;
}
}
- if (mRootLeash != null) {
- mRootLeash.release();
+ for (int i = 0; i < mRoots.size(); ++i) {
+ mRoots.get(i).mLeash.release();
}
}
@@ -476,10 +513,11 @@
for (int i = 0; i < mChanges.size(); ++i) {
out.mChanges.add(mChanges.get(i).localRemoteCopy());
}
- out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+ for (int i = 0; i < mRoots.size(); ++i) {
+ out.mRoots.add(mRoots.get(i).localRemoteCopy());
+ }
// Doesn't have any native stuff, so no need for actual copy
out.mOptions = mOptions;
- out.mRootOffset.set(mRootOffset);
return out;
}
@@ -496,6 +534,8 @@
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private boolean mAllowEnterPip;
+ private int mStartDisplayId = INVALID_DISPLAY;
+ private int mEndDisplayId = INVALID_DISPLAY;
private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
/**
@@ -526,6 +566,8 @@
mEndRelOffset.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mAllowEnterPip = in.readBoolean();
+ mStartDisplayId = in.readInt();
+ mEndDisplayId = in.readInt();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
mEndFixedRotation = in.readInt();
@@ -546,6 +588,8 @@
out.mEndRelOffset.set(mEndRelOffset);
out.mTaskInfo = mTaskInfo;
out.mAllowEnterPip = mAllowEnterPip;
+ out.mStartDisplayId = mStartDisplayId;
+ out.mEndDisplayId = mEndDisplayId;
out.mStartRotation = mStartRotation;
out.mEndRotation = mEndRotation;
out.mEndFixedRotation = mEndFixedRotation;
@@ -608,6 +652,12 @@
}
/** Sets the start and end rotation of this container. */
+ public void setDisplayId(int start, int end) {
+ mStartDisplayId = start;
+ mEndDisplayId = end;
+ }
+
+ /** Sets the start and end rotation of this container. */
public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
mStartRotation = start;
mEndRotation = end;
@@ -725,6 +775,14 @@
return mAllowEnterPip;
}
+ public int getStartDisplayId() {
+ return mStartDisplayId;
+ }
+
+ public int getEndDisplayId() {
+ return mEndDisplayId;
+ }
+
@Surface.Rotation
public int getStartRotation() {
return mStartRotation;
@@ -776,6 +834,8 @@
mEndRelOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
dest.writeBoolean(mAllowEnterPip);
+ dest.writeInt(mStartDisplayId);
+ dest.writeInt(mEndDisplayId);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
dest.writeInt(mEndFixedRotation);
@@ -822,6 +882,11 @@
if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
sb.append(" eo="); sb.append(mEndRelOffset);
}
+ sb.append(" d=");
+ if (mStartDisplayId != mEndDisplayId) {
+ sb.append(mStartDisplayId).append("->");
+ }
+ sb.append(mEndDisplayId);
if (mStartRotation != mEndRotation) {
sb.append(" r="); sb.append(mStartRotation);
sb.append("->"); sb.append(mEndRotation);
@@ -1108,4 +1173,86 @@
};
}
}
+
+ /**
+ * An animation root in a transition. There is one of these for each display that contains
+ * participants. It will be placed, in z-order, right above the top-most participant and at the
+ * same position in the hierarchy. As a result, if all participants are animating within a
+ * part of the screen, the root-leash will only be in that part of the screen. In these cases,
+ * it's relative position (from the screen) is stored in {@link Root#getOffset}.
+ */
+ public static final class Root implements Parcelable {
+ private final int mDisplayId;
+ private final SurfaceControl mLeash;
+ private final Point mOffset = new Point();
+
+ public Root(int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
+ mDisplayId = displayId;
+ mLeash = leash;
+ mOffset.set(offsetLeft, offsetTop);
+ }
+
+ private Root(Parcel in) {
+ mDisplayId = in.readInt();
+ mLeash = new SurfaceControl();
+ mLeash.readFromParcel(in);
+ mLeash.setUnreleasedWarningCallSite("TransitionInfo.Root");
+ mOffset.readFromParcel(in);
+ }
+
+ private Root localRemoteCopy() {
+ return new Root(mDisplayId, new SurfaceControl(mLeash, "localRemote"),
+ mOffset.x, mOffset.y);
+ }
+
+ /** @return the id of the display this root is on. */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** @return the root's leash. Surfaces should be parented to this while animating. */
+ @NonNull
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
+ /** @return the offset (relative to its screen) of the root leash. */
+ @NonNull
+ public Point getOffset() {
+ return mOffset;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDisplayId);
+ mLeash.writeToParcel(dest, flags);
+ mOffset.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public static final Creator<Root> CREATOR =
+ new Creator<Root>() {
+ @Override
+ public Root createFromParcel(Parcel in) {
+ return new Root(in);
+ }
+
+ @Override
+ public Root[] newArray(int size) {
+ return new Root[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return mDisplayId + "@" + mOffset + ":" + mLeash;
+ }
+ }
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a3209f6..fabb089 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -131,6 +131,19 @@
}
/**
+ * Sets the densityDpi value in the configuration for the given container.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setDensityDpi(@NonNull WindowContainerToken container,
+ int densityDpi) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mConfiguration.densityDpi = densityDpi;
+ chg.mConfigSetMask |= ActivityInfo.CONFIG_DENSITY;
+ return this;
+ }
+
+ /**
* Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
* has finished the enter animation with the given bounds.
*/
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 429156f..01e577f 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -63,10 +63,17 @@
@NonNull
public final Rect bounds;
- WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+ /**
+ * True if the window is a trusted overlay.
+ */
+ public final boolean isTrustedOverlay;
+
+ WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds,
+ int inputConfig) {
this.windowToken = windowToken;
this.name = name;
this.bounds = bounds;
+ this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
}
}
@@ -129,7 +136,8 @@
}
var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
handle.frameBottom);
- windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+ windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds,
+ handle.inputConfig));
}
return windowInfos;
}
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 57bd3f9..d521866 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -20,6 +20,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.icu.text.CaseMap;
import android.icu.text.ListFormatter;
+import android.icu.text.NumberingSystem;
import android.icu.util.ULocale;
import android.os.LocaleList;
import android.text.TextUtils;
@@ -173,6 +174,21 @@
}
/**
+ * Returns numbering system value of a locale for display in the provided locale.
+ *
+ * @param locale The locale whose key value is displayed.
+ * @param displayLocale The locale in which to display the key value.
+ * @return The string of numbering system.
+ */
+ public static String getDisplayNumberingSystemKeyValue(
+ Locale locale, Locale displayLocale) {
+ ULocale uLocale = new ULocale.Builder()
+ .setUnicodeLocaleKeyword("nu", NumberingSystem.getInstance(locale).getName())
+ .build();
+ return uLocale.getDisplayKeywordValue("numbers", ULocale.forLocale(displayLocale));
+ }
+
+ /**
* Adds the likely subtags for a provided locale ID.
*
* @param locale the locale to maximize.
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 685bd9a..5dfc0ea 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -61,6 +61,7 @@
private int mTopDistance = 0;
private CharSequence mTitle = null;
private OnActionExpandListener mOnActionExpandListener;
+ private boolean mIsNumberingSystem = false;
/**
* Other classes can register to be notified when a locale was selected.
@@ -90,6 +91,18 @@
boolean hasSpecificPackageName();
}
+ private static LocalePickerWithRegion createNumberingSystemPicker(
+ LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+ LocaleCollectorBase localePickerCollector) {
+ LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+ localePicker.setOnActionExpandListener(onActionExpandListener);
+ localePicker.setIsNumberingSystem(true);
+ boolean shouldShowTheList = localePicker.setListener(listener, parent,
+ translatedOnly, localePickerCollector);
+ return shouldShowTheList ? localePicker : null;
+ }
+
private static LocalePickerWithRegion createCountryPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
boolean translatedOnly, OnActionExpandListener onActionExpandListener,
@@ -128,6 +141,10 @@
return localePicker;
}
+ private void setIsNumberingSystem(boolean isNumberingSystem) {
+ mIsNumberingSystem = isNumberingSystem;
+ }
+
/**
* Sets the listener and initializes the locale list.
*
@@ -184,6 +201,7 @@
final boolean hasSpecificPackageName =
mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
+ mAdapter.setNumberingSystemMode(mIsNumberingSystem);
final LocaleHelper.LocaleInfoComparator comp =
new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
mAdapter.sort(comp);
@@ -213,7 +231,6 @@
@Override
public void onResume() {
super.onResume();
-
if (mParentLocale != null) {
getActivity().setTitle(mParentLocale.getFullNameNative());
} else {
@@ -250,16 +267,28 @@
// Special case for resetting the app locale to equal the system locale.
boolean isSystemLocale = locale.isSystemLocale();
boolean isRegionLocale = locale.getParent() != null;
+ boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems();
- if (isSystemLocale || isRegionLocale) {
+ if (isSystemLocale
+ || (isRegionLocale && !mayHaveDifferentNumberingSystem)
+ || mIsNumberingSystem) {
if (mListener != null) {
mListener.onLocaleSelected(locale);
}
returnToParentFrame();
} else {
- LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
- mListener, locale, mTranslatedOnly /* translate only */,
- mOnActionExpandListener, this.mLocalePickerCollector);
+ LocalePickerWithRegion selector;
+ if (mayHaveDifferentNumberingSystem) {
+ selector =
+ LocalePickerWithRegion.createNumberingSystemPicker(
+ mListener, locale, mTranslatedOnly /* translate only */,
+ mOnActionExpandListener, this.mLocalePickerCollector);
+ } else {
+ selector = LocalePickerWithRegion.createCountryPicker(
+ mListener, locale, mTranslatedOnly /* translate only */,
+ mOnActionExpandListener, this.mLocalePickerCollector);
+ }
+
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 8b41829..bcbfdc9 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -39,6 +39,9 @@
import java.util.Set;
public class LocaleStore {
+ private static final int TIER_LANGUAGE = 1;
+ private static final int TIER_REGION = 2;
+ private static final int TIER_NUMBERING = 3;
private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
private static final String TAG = LocaleStore.class.getSimpleName();
private static boolean sFullyInitialized = false;
@@ -68,10 +71,13 @@
private String mFullCountryNameNative;
private String mLangScriptKey;
+ private boolean mHasNumberingSystems;
+
private LocaleInfo(Locale locale) {
this.mLocale = locale;
this.mId = locale.toLanguageTag();
this.mParent = getParent(locale);
+ this.mHasNumberingSystems = false;
this.mIsChecked = false;
this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
this.mIsTranslated = false;
@@ -93,6 +99,11 @@
.build();
}
+ /** Return true if there are any same locales with different numbering system. */
+ public boolean hasNumberingSystems() {
+ return mHasNumberingSystems;
+ }
+
@Override
public String toString() {
return mId;
@@ -195,6 +206,10 @@
}
}
+ String getNumberingSystem() {
+ return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
+ }
+
String getContentDescription(boolean countryMode) {
if (countryMode) {
return getFullCountryNameInUiLanguage();
@@ -383,6 +398,12 @@
final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+ Set<Locale> numberSystemLocaleList = new HashSet<>();
+ for (String localeId : LocalePicker.getSupportedLocales(context)) {
+ if (Locale.forLanguageTag(localeId).getUnicodeLocaleType("nu") != null) {
+ numberSystemLocaleList.add(Locale.forLanguageTag(localeId));
+ }
+ }
for (String localeId : LocalePicker.getSupportedLocales(context)) {
if (localeId.isEmpty()) {
throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
@@ -403,6 +424,12 @@
if (simCountries.contains(li.getLocale().getCountry())) {
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
+ numberSystemLocaleList.forEach(l -> {
+ if (li.getLocale().stripExtensions().equals(l.stripExtensions())) {
+ li.mHasNumberingSystems = true;
+ }
+ });
+
sLocaleCache.put(li.getId(), li);
final Locale parent = li.getParent();
if (parent != null) {
@@ -445,20 +472,43 @@
sFullyInitialized = true;
}
- private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
- if (ignorables.contains(li.getId())) return 0;
- if (li.mIsPseudo) return 2;
- if (translatedOnly && !li.isTranslated()) return 0;
- if (li.getParent() != null) return 2;
- return 0;
+ private static boolean isShallIgnore(
+ Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
+ if (ignorables.stream().anyMatch(tag ->
+ Locale.forLanguageTag(tag).stripExtensions()
+ .equals(li.getLocale().stripExtensions()))) {
+ return true;
+ }
+ if (li.mIsPseudo) return false;
+ if (translatedOnly && !li.isTranslated()) return true;
+ if (li.getParent() != null) return false;
+ return true;
+ }
+
+ private static int getLocaleTier(LocaleInfo parent) {
+ if (parent == null) {
+ return TIER_LANGUAGE;
+ } else if (parent.getLocale().getCountry().isEmpty()) {
+ return TIER_REGION;
+ } else {
+ return TIER_NUMBERING;
+ }
}
/**
* Returns a list of locales for language or region selection.
+ *
* If the parent is null, then it is the language list.
+ *
* If it is not null, then the list will contain all the locales that belong to that parent.
* Example: if the parent is "ar", then the region list will contain all Arabic locales.
- * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+ * (this is not language based, but language-script, so that it works for zh-Hant and so on.)
+ *
+ * If it is not null and has country, then the list will contain all locales with that parent's
+ * language and country, i.e. containing alternate numbering systems.
+ *
+ * Example: if the parent is "ff-Adlm-BF", then the numbering list will contain all
+ * Fula (Adlam, Burkina Faso) i.e. "ff-Adlm-BF" and "ff-Adlm-BF-u-nu-latn"
*/
@UnsupportedAppUsage
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
@@ -478,28 +528,49 @@
*/
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
- fillCache(context);
- String parentId = parent == null ? null : parent.getId();
- HashSet<LocaleInfo> result = new HashSet<>();
+ if (context != null) {
+ fillCache(context);
+ }
HashMap<String, LocaleInfo> supportedLcoaleInfos =
explicitLocales == null
? sLocaleCache
: convertExplicitLocales(explicitLocales, sLocaleCache.values());
+ return getTierLocales(ignorables, parent, translatedOnly, supportedLcoaleInfos);
+ }
+ private static Set<LocaleInfo> getTierLocales(
+ Set<String> ignorables,
+ LocaleInfo parent,
+ boolean translatedOnly,
+ HashMap<String, LocaleInfo> supportedLcoaleInfos) {
+
+ boolean hasTargetParent = parent != null;
+ String parentId = hasTargetParent ? parent.getId() : null;
+ HashSet<LocaleInfo> result = new HashSet<>();
for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
- int level = getLevel(ignorables, li, translatedOnly);
- if (level == 2) {
- if (parent != null) { // region selection
- if (parentId.equals(li.getParent().toLanguageTag())) {
- result.add(li);
- }
- } else { // language selection
+ if (isShallIgnore(ignorables, li, translatedOnly)) {
+ continue;
+ }
+ switch(getLocaleTier(parent)) {
+ case TIER_LANGUAGE:
if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
result.add(li);
} else {
- result.add(getLocaleInfo(li.getParent()));
+ result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos));
}
- }
+ break;
+ case TIER_REGION:
+ if (parentId.equals(li.getParent().toLanguageTag())) {
+ result.add(getLocaleInfo(
+ li.getLocale().stripExtensions(), supportedLcoaleInfos));
+ }
+ break;
+ case TIER_NUMBERING:
+ if (parent.getLocale().stripExtensions()
+ .equals(li.getLocale().stripExtensions())) {
+ result.add(li);
+ }
+ break;
}
}
return result;
@@ -538,18 +609,21 @@
}
private static LocaleList matchLocaleFromSupportedLocaleList(
- LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+ LocaleList explicitLocales, Collection<LocaleInfo> localeInfos) {
+ if (localeInfos == null) {
+ return explicitLocales;
+ }
//TODO: Adds a function for unicode extension if needed.
Locale[] resultLocales = new Locale[explicitLocales.size()];
for (int i = 0; i < explicitLocales.size(); i++) {
- Locale locale = explicitLocales.get(i).stripExtensions();
+ Locale locale = explicitLocales.get(i);
if (!TextUtils.isEmpty(locale.getCountry())) {
- for (LocaleInfo localeInfo :localeinfo) {
+ for (LocaleInfo localeInfo :localeInfos) {
if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
&& TextUtils.equals(locale.getCountry(),
localeInfo.getLocale().getCountry())) {
resultLocales[i] = localeInfo.getLocale();
- continue;
+ break;
}
}
}
@@ -562,18 +636,23 @@
@UnsupportedAppUsage
public static LocaleInfo getLocaleInfo(Locale locale) {
+ return getLocaleInfo(locale, sLocaleCache);
+ }
+
+ private static LocaleInfo getLocaleInfo(
+ Locale locale, HashMap<String, LocaleInfo> localeInfos) {
String id = locale.toLanguageTag();
LocaleInfo result;
- if (!sLocaleCache.containsKey(id)) {
+ if (!localeInfos.containsKey(id)) {
// Locale preferences can modify the language tag to current system languages, so we
// need to check the input locale without extra u extension except numbering system.
Locale filteredLocale = new Locale.Builder()
.setLocale(locale.stripExtensions())
.setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu"))
.build();
- if (sLocaleCache.containsKey(filteredLocale.toLanguageTag())) {
+ if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
result = new LocaleInfo(locale);
- LocaleInfo localeInfo = sLocaleCache.get(filteredLocale.toLanguageTag());
+ LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
// This locale is included in supported locales, so follow the settings
// of supported locales.
result.mIsPseudo = localeInfo.mIsPseudo;
@@ -582,9 +661,9 @@
return result;
}
result = new LocaleInfo(locale);
- sLocaleCache.put(id, result);
+ localeInfos.put(id, result);
} else {
- result = sLocaleCache.get(id);
+ result = localeInfos.get(id);
}
return result;
}
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index a61a6d7..08de4dfb 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -64,6 +64,7 @@
protected ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
protected int mSuggestionCount;
protected final boolean mCountryMode;
+ protected boolean mIsNumberingMode;
protected LayoutInflater mInflater;
protected Locale mDisplayLocale = null;
@@ -89,6 +90,14 @@
}
}
+ public void setNumberingSystemMode(boolean isNumberSystemMode) {
+ mIsNumberingMode = isNumberSystemMode;
+ }
+
+ public boolean getIsForNumberingSystem() {
+ return mIsNumberingMode;
+ }
+
@Override
public boolean areAllItemsEnabled() {
return false;
@@ -209,7 +218,6 @@
if (convertView == null && mInflater == null) {
mInflater = LayoutInflater.from(parent.getContext());
}
-
int itemType = getItemViewType(position);
View itemView = getNewViewIfNeeded(convertView, parent, itemType, position);
switch (itemType) {
@@ -217,13 +225,13 @@
case TYPE_HEADER_ALL_OTHERS:
TextView textView = (TextView) itemView;
if (itemType == TYPE_HEADER_SUGGESTED) {
- if (mCountryMode) {
+ if (mCountryMode && !mIsNumberingMode) {
setTextTo(textView, R.string.language_picker_regions_section_suggested);
} else {
setTextTo(textView, R.string.language_picker_section_suggested);
}
} else {
- if (mCountryMode) {
+ if (mCountryMode && !mIsNumberingMode) {
setTextTo(textView, R.string.region_picker_section_all);
} else {
setTextTo(textView, R.string.language_picker_section_all);
@@ -419,9 +427,11 @@
private void updateTextView(View convertView, TextView text, int position) {
LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
- text.setText(item.getLabel(mCountryMode));
+ text.setText(mIsNumberingMode
+ ? item.getNumberingSystem() : item.getLabel(mCountryMode));
text.setTextLocale(item.getLocale());
- text.setContentDescription(item.getContentDescription(mCountryMode));
+ text.setContentDescription(mIsNumberingMode
+ ? item.getNumberingSystem() : item.getContentDescription(mCountryMode));
if (mCountryMode) {
int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
//noinspection ResourceType
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index bce0d60..f6bcc46 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -27,12 +27,12 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_OFF;
import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_ON;
import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -72,7 +72,8 @@
STATE_NAMES = new String[STATE_COUNT];
STATE_NAMES[STATE_PERSISTENT] = "Persist";
STATE_NAMES[STATE_TOP] = "Top";
- STATE_NAMES[STATE_BOUND_TOP_OR_FGS] = "BTopFgs";
+ STATE_NAMES[STATE_BOUND_FGS] = "BFgs";
+ STATE_NAMES[STATE_BOUND_TOP] = "BTop";
STATE_NAMES[STATE_FGS] = "Fgs";
STATE_NAMES[STATE_IMPORTANT_FOREGROUND] = "ImpFg";
STATE_NAMES[STATE_IMPORTANT_BACKGROUND] = "ImpBg";
@@ -83,14 +84,14 @@
STATE_NAMES[STATE_HEAVY_WEIGHT] = "HeavyWt";
STATE_NAMES[STATE_HOME] = "Home";
STATE_NAMES[STATE_LAST_ACTIVITY] = "LastAct";
- STATE_NAMES[STATE_CACHED_ACTIVITY] = "CchAct";
- STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT] = "CchCAct";
- STATE_NAMES[STATE_CACHED_EMPTY] = "CchEmty";
+ STATE_NAMES[STATE_CACHED] = "Cached";
+ STATE_NAMES[STATE_FROZEN] = "Frozen";
STATE_LABELS = new String[STATE_COUNT];
STATE_LABELS[STATE_PERSISTENT] = "Persistent";
STATE_LABELS[STATE_TOP] = " Top";
- STATE_LABELS[STATE_BOUND_TOP_OR_FGS] = "Bnd TopFgs";
+ STATE_LABELS[STATE_BOUND_FGS] = " Bnd Fgs";
+ STATE_LABELS[STATE_BOUND_TOP] = " Bnd Top";
STATE_LABELS[STATE_FGS] = " Fgs";
STATE_LABELS[STATE_IMPORTANT_FOREGROUND] = " Imp Fg";
STATE_LABELS[STATE_IMPORTANT_BACKGROUND] = " Imp Bg";
@@ -101,16 +102,16 @@
STATE_LABELS[STATE_HEAVY_WEIGHT] = " Heavy Wgt";
STATE_LABELS[STATE_HOME] = " (Home)";
STATE_LABELS[STATE_LAST_ACTIVITY] = "(Last Act)";
- STATE_LABELS[STATE_CACHED_ACTIVITY] = " (Cch Act)";
- STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT] = "(Cch CAct)";
- STATE_LABELS[STATE_CACHED_EMPTY] = "(Cch Emty)";
+ STATE_LABELS[STATE_CACHED] = " (Cached)";
+ STATE_LABELS[STATE_FROZEN] = " Frozen";
STATE_LABEL_CACHED = " (Cached)";
STATE_LABEL_TOTAL = " TOTAL";
STATE_NAMES_CSV = new String[STATE_COUNT];
STATE_NAMES_CSV[STATE_PERSISTENT] = "pers";
STATE_NAMES_CSV[STATE_TOP] = "top";
- STATE_NAMES_CSV[STATE_BOUND_TOP_OR_FGS] = "btopfgs";
+ STATE_NAMES_CSV[STATE_BOUND_FGS] = "bfgs";
+ STATE_NAMES_CSV[STATE_BOUND_TOP] = "btop";
STATE_NAMES_CSV[STATE_FGS] = "fgs";
STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND] = "impfg";
STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND] = "impbg";
@@ -121,14 +122,14 @@
STATE_NAMES_CSV[STATE_HEAVY_WEIGHT] = "heavy";
STATE_NAMES_CSV[STATE_HOME] = "home";
STATE_NAMES_CSV[STATE_LAST_ACTIVITY] = "lastact";
- STATE_NAMES_CSV[STATE_CACHED_ACTIVITY] = "cch-activity";
- STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT] = "cch-aclient";
- STATE_NAMES_CSV[STATE_CACHED_EMPTY] = "cch-empty";
+ STATE_NAMES_CSV[STATE_CACHED] = "cached";
+ STATE_NAMES_CSV[STATE_FROZEN] = "frzn";
STATE_TAGS = new String[STATE_COUNT];
STATE_TAGS[STATE_PERSISTENT] = "p";
STATE_TAGS[STATE_TOP] = "t";
- STATE_TAGS[STATE_BOUND_TOP_OR_FGS] = "d";
+ STATE_TAGS[STATE_BOUND_FGS] = "y";
+ STATE_TAGS[STATE_BOUND_TOP] = "z";
STATE_TAGS[STATE_FGS] = "g";
STATE_TAGS[STATE_IMPORTANT_FOREGROUND] = "f";
STATE_TAGS[STATE_IMPORTANT_BACKGROUND] = "b";
@@ -139,15 +140,14 @@
STATE_TAGS[STATE_HEAVY_WEIGHT] = "w";
STATE_TAGS[STATE_HOME] = "h";
STATE_TAGS[STATE_LAST_ACTIVITY] = "l";
- STATE_TAGS[STATE_CACHED_ACTIVITY] = "a";
- STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT] = "c";
- STATE_TAGS[STATE_CACHED_EMPTY] = "e";
+ STATE_TAGS[STATE_CACHED] = "a";
+ STATE_TAGS[STATE_FROZEN] = "e";
STATE_PROTO_ENUMS = new int[STATE_COUNT];
STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsEnums.PROCESS_STATE_PERSISTENT;
STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsEnums.PROCESS_STATE_TOP;
- STATE_PROTO_ENUMS[STATE_BOUND_TOP_OR_FGS] =
- ProcessStatsEnums.PROCESS_STATE_BOUND_TOP_OR_FGS;
+ STATE_PROTO_ENUMS[STATE_BOUND_FGS] = ProcessStatsEnums.PROCESS_STATE_BOUND_FGS;
+ STATE_PROTO_ENUMS[STATE_BOUND_TOP] = ProcessStatsEnums.PROCESS_STATE_BOUND_TOP;
STATE_PROTO_ENUMS[STATE_FGS] = ProcessStatsEnums.PROCESS_STATE_FGS;
STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] =
ProcessStatsEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
@@ -161,10 +161,8 @@
STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsEnums.PROCESS_STATE_HEAVY_WEIGHT;
STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsEnums.PROCESS_STATE_HOME;
STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_LAST_ACTIVITY;
- STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
- STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] =
- ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY;
+ STATE_PROTO_ENUMS[STATE_CACHED] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
+ STATE_PROTO_ENUMS[STATE_FROZEN] = ProcessStatsEnums.PROCESS_STATE_FROZEN;
// Remap states, as defined by ProcessStats.java, to a reduced subset of states for data
// aggregation / size reduction purposes.
@@ -173,7 +171,9 @@
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_PERSISTENT;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_TOP] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_TOP;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP_OR_FGS] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_FGS] =
+ ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FGS] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_FGS;
@@ -196,11 +196,9 @@
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_LAST_ACTIVITY] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] =
- ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_EMPTY] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FROZEN] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 818a503..fff778c 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -28,10 +28,9 @@
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
@@ -85,9 +84,9 @@
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
- STATE_BOUND_TOP_OR_FGS, // ActivityManager.PROCESS_STATE_BOUND_TOP
+ STATE_BOUND_TOP, // ActivityManager.PROCESS_STATE_BOUND_TOP
STATE_FGS, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- STATE_BOUND_TOP_OR_FGS, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ STATE_BOUND_FGS, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
@@ -98,10 +97,10 @@
STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
STATE_HOME, // ActivityManager.PROCESS_STATE_HOME
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
- STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
- STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
- STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
- STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
public static final Comparator<ProcessState> COMPARATOR = new Comparator<ProcessState>() {
@@ -926,8 +925,11 @@
screenStates, memStates, new int[] { STATE_PERSISTENT }, now, totalTime, true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_TOP],
screenStates, memStates, new int[] {STATE_TOP}, now, totalTime, true);
- dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP_OR_FGS],
- screenStates, memStates, new int[] { STATE_BOUND_TOP_OR_FGS}, now, totalTime,
+ dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP],
+ screenStates, memStates, new int[] { STATE_BOUND_TOP }, now, totalTime,
+ true);
+ dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_FGS],
+ screenStates, memStates, new int[] { STATE_BOUND_FGS }, now, totalTime,
true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_FGS],
screenStates, memStates, new int[] { STATE_FGS}, now, totalTime,
@@ -953,9 +955,6 @@
screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_LAST_ACTIVITY],
screenStates, memStates, new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true);
- dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_CACHED,
- screenStates, memStates, new int[] {STATE_CACHED_ACTIVITY,
- STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY}, now, totalTime, true);
}
public void dumpProcessState(PrintWriter pw, String prefix,
@@ -1563,7 +1562,10 @@
case STATE_TOP:
topMs += duration;
break;
- case STATE_BOUND_TOP_OR_FGS:
+ case STATE_BOUND_FGS:
+ boundFgsMs += duration;
+ break;
+ case STATE_BOUND_TOP:
boundTopMs += duration;
break;
case STATE_FGS:
@@ -1583,13 +1585,10 @@
case STATE_PERSISTENT:
otherMs += duration;
break;
- case STATE_CACHED_ACTIVITY:
- case STATE_CACHED_ACTIVITY_CLIENT:
- case STATE_CACHED_EMPTY:
+ case STATE_CACHED:
cachedMs += duration;
break;
- // TODO (b/261910877) Add support for tracking boundFgsMs and
- // frozenMs.
+ // TODO (b/261910877) Add support for tracking frozenMs.
}
}
statsEventOutput.write(
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index f3ed09a..3ce234b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -81,21 +81,21 @@
public static final int STATE_NOTHING = -1;
public static final int STATE_PERSISTENT = 0;
public static final int STATE_TOP = 1;
- public static final int STATE_BOUND_TOP_OR_FGS = 2;
+ public static final int STATE_BOUND_TOP = 2;
public static final int STATE_FGS = 3;
- public static final int STATE_IMPORTANT_FOREGROUND = 4;
- public static final int STATE_IMPORTANT_BACKGROUND = 5;
- public static final int STATE_BACKUP = 6;
- public static final int STATE_SERVICE = 7;
- public static final int STATE_SERVICE_RESTARTING = 8;
- public static final int STATE_RECEIVER = 9;
- public static final int STATE_HEAVY_WEIGHT = 10;
- public static final int STATE_HOME = 11;
- public static final int STATE_LAST_ACTIVITY = 12;
- public static final int STATE_CACHED_ACTIVITY = 13;
- public static final int STATE_CACHED_ACTIVITY_CLIENT = 14;
- public static final int STATE_CACHED_EMPTY = 15;
- public static final int STATE_COUNT = STATE_CACHED_EMPTY+1;
+ public static final int STATE_BOUND_FGS = 4;
+ public static final int STATE_IMPORTANT_FOREGROUND = 5;
+ public static final int STATE_IMPORTANT_BACKGROUND = 6;
+ public static final int STATE_BACKUP = 7;
+ public static final int STATE_SERVICE = 8;
+ public static final int STATE_SERVICE_RESTARTING = 9;
+ public static final int STATE_RECEIVER = 10;
+ public static final int STATE_HEAVY_WEIGHT = 11;
+ public static final int STATE_HOME = 12;
+ public static final int STATE_LAST_ACTIVITY = 13;
+ public static final int STATE_CACHED = 14;
+ public static final int STATE_FROZEN = 15;
+ public static final int STATE_COUNT = STATE_FROZEN + 1;
public static final int PSS_SAMPLE_COUNT = 0;
public static final int PSS_MINIMUM = 1;
@@ -154,9 +154,10 @@
public static final int[] ALL_SCREEN_ADJ = new int[] { ADJ_SCREEN_OFF, ADJ_SCREEN_ON };
public static final int[] NON_CACHED_PROC_STATES = new int[] {
- STATE_PERSISTENT, STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS,
+ STATE_PERSISTENT, STATE_TOP, STATE_FGS,
STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
- STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
+ STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT,
+ STATE_BOUND_TOP, STATE_BOUND_FGS
};
public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -165,11 +166,11 @@
};
public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
- STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
+ STATE_TOP, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
- STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
- STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
+ STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED,
+ STATE_BOUND_TOP, STATE_BOUND_FGS, STATE_FROZEN
};
// Should report process stats.
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 7ae63b1..928a097 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
@@ -448,17 +449,7 @@
mEnabled = DEFAULT_ENABLED;
final Context context = ActivityThread.currentApplication();
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
- // Post initialization to the background in case we're running on the main thread.
- mWorker.getThreadHandler().post(
- () -> mPropertiesChangedListener.onPropertiesChanged(
- DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
- new HandlerExecutor(mWorker.getThreadHandler()),
- mPropertiesChangedListener);
- } else {
+ if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
if (DEBUG) {
Log.d(TAG, "Initialized the InteractionJankMonitor."
+ " (No READ_DEVICE_CONFIG permission to change configs)"
@@ -467,7 +458,25 @@
+ ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+ ", package=" + context.getPackageName());
}
+ return;
}
+
+ // Post initialization to the background in case we're running on the main thread.
+ mWorker.getThreadHandler().post(
+ () -> {
+ try {
+ mPropertiesChangedListener.onPropertiesChanged(
+ DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker.getThreadHandler()),
+ mPropertiesChangedListener);
+ } catch (SecurityException ex) {
+ Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+ + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + context.getPackageName());
+ }
+ });
}
/**
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 076e4e1..1505ccc 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -185,8 +185,13 @@
private static void preloadSharedLibraries() {
Log.i(TAG, "Preloading shared libraries...");
System.loadLibrary("android");
- System.loadLibrary("compiler_rt");
System.loadLibrary("jnigraphics");
+
+ // TODO(b/206676167): This library is only used for renderscript today. When renderscript is
+ // removed, this load can be removed as well.
+ if (!SystemProperties.getBoolean("config.disable_renderscript", false)) {
+ System.loadLibrary("compiler_rt");
+ }
}
native private static void nativePreloadAppProcessHALs();
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 3a393b6..69d3d6a 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -195,6 +195,8 @@
UserHandle.CURRENT)) {
mScreenshotConnection = conn;
handler.postDelayed(mScreenshotTimeout, timeoutMs);
+ } else {
+ mContext.unbindService(conn);
}
} else {
Messenger messenger = new Messenger(mScreenshotService);
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 739055e..0c3ff6c 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -169,21 +169,25 @@
gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
vsyncEventData.frameInterval);
- jobjectArray frameTimelinesObj = reinterpret_cast<jobjectArray>(
- env->GetObjectField(vsyncEventDataObj.get(),
- gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
- .frameTimelines));
+ ScopedLocalRef<jobjectArray>
+ frameTimelinesObj(env,
+ reinterpret_cast<jobjectArray>(
+ env->GetObjectField(vsyncEventDataObj.get(),
+ gDisplayEventReceiverClassInfo
+ .vsyncEventDataClassInfo
+ .frameTimelines)));
for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
- jobject frameTimelineObj = env->GetObjectArrayElement(frameTimelinesObj, i);
- env->SetLongField(frameTimelineObj,
+ ScopedLocalRef<jobject>
+ frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i));
+ env->SetLongField(frameTimelineObj.get(),
gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId,
frameTimeline.vsyncId);
- env->SetLongField(frameTimelineObj,
+ env->SetLongField(frameTimelineObj.get(),
gDisplayEventReceiverClassInfo.frameTimelineClassInfo
.expectedPresentationTime,
frameTimeline.expectedPresentationTime);
- env->SetLongField(frameTimelineObj,
+ env->SetLongField(frameTimelineObj.get(),
gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline,
frameTimeline.deadlineTimestamp);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 1b812d1..d62f1cf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1805,10 +1805,15 @@
if (!is_system_server && getuid() == 0) {
const int rc = createProcessGroup(uid, getpid());
if (rc != 0) {
- fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
- "CONFIG_CGROUP_CPUACCT?")
- : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
- /* pid= */ 0, strerror(-rc)));
+ if (rc == -ESRCH) {
+ // If process is dead, treat this as a non-fatal error
+ ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
+ } else {
+ fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
+ "CONFIG_CGROUP_CPUACCT?")
+ : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
+ /* pid= */ 0, strerror(-rc)));
+ }
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 496cc3a..70a1354 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3161,7 +3161,7 @@
<!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
device. -->
<permission android:name="android.permission.QUERY_USERS"
- android:protectionLevel="signature|role" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows an application to access data blobs across users. -->
<permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
@@ -4492,7 +4492,7 @@
<!-- Allows an application to be able to store and retrieve credentials from a remote
device.
- @hide @SystemApi -->
+ <p>Protection level: signature|privileged|role -->
<permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
android:protectionLevel="signature|privileged|role" />
@@ -5303,12 +5303,12 @@
{@link android.Manifest.permission#USE_EXACT_ALARM} once it targets API
{@link android.os.Build.VERSION_CODES#TIRAMISU}. All apps using exact alarms for secondary
features (which should still be user facing) should continue using this permission.
- <p>Protection level: appop
+ <p>Protection level: signature|privileged|appop
-->
<permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:label="@string/permlab_schedule_exact_alarm"
android:description="@string/permdesc_schedule_exact_alarm"
- android:protectionLevel="normal|appop"/>
+ android:protectionLevel="signature|privileged|appop"/>
<!-- Allows apps to use exact alarms just like with {@link
android.Manifest.permission#SCHEDULE_EXACT_ALARM} but without needing to request this
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index d07ad89..1ad3acd 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -56,6 +56,7 @@
android:paddingTop="16dp"
android:layout_below="@id/icon"
android:layout_centerHorizontal="true"
+ android:fontFamily="@string/config_headlineFontFamily"
android:textSize="24sp"
android:lineHeight="32sp"
android:gravity="center"
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index e752431..8eae064 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -19,23 +19,28 @@
android:id="@+id/expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_header_height"
android:layout_gravity="top|end"
android:contentDescription="@string/expand_button_content_description_collapsed"
- android:padding="16dp"
+ android:paddingHorizontal="16dp"
>
<LinearLayout
android:id="@+id/expand_button_pill"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_expand_button_pill_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_expand_button_pill_height"
android:orientation="horizontal"
android:background="@drawable/expand_button_pill_bg"
+ android:gravity="center_vertical"
+ android:layout_gravity="center_vertical"
>
<TextView
android:id="@+id/expand_button_number"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_expand_button_pill_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_expand_button_pill_height"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:gravity="center_vertical"
android:paddingStart="8dp"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index fd787f6..16a8bb7 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -79,7 +79,8 @@
<NotificationTopLineView
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 1b3bd26..76bcc96 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -29,7 +29,8 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="88dp"
+ android:layout_height="wrap_content"
+ android:minHeight="88dp"
android:orientation="horizontal"
>
@@ -41,6 +42,7 @@
android:layout_marginStart="@dimen/conversation_content_start"
android:orientation="vertical"
android:minHeight="68dp"
+ android:paddingBottom="@dimen/notification_headerless_margin_twoline"
>
<include
@@ -49,7 +51,10 @@
android:layout_height="wrap_content"
/>
- <include layout="@layout/notification_template_text" />
+ <include layout="@layout/notification_template_text"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_text_height"
+ />
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 95ddc2e..df32d30 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,7 +19,8 @@
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_min_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_min_height"
android:tag="media"
>
@@ -77,7 +78,8 @@
<NotificationTopLineView
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index bef1d0b..3e82bd1 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -102,7 +102,8 @@
<NotificationTopLineView
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index b35481d..97e753e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -227,6 +227,12 @@
<string-array name="device_state_notification_thermal_contents">
<item>@string/concurrent_display_notification_thermal_content</item>
</string-array>
+ <string-array name="device_state_notification_power_save_titles">
+ <item>@string/concurrent_display_notification_power_save_title</item>
+ </string-array>
+ <string-array name="device_state_notification_power_save_contents">
+ <item>@string/concurrent_display_notification_power_save_content</item>
+ </string-array>
<!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner of the
demo device provisioning permissions. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4d2747a..2ed1817 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -711,6 +711,9 @@
mode. -->
<integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
+ <!-- Timeout for receiving the keyguard drawn event from System UI. -->
+ <integer name="config_keyguardDrawnTimeout">1000</integer>
+
<!-- Indicates that the device supports having more than one internal display on at the same
time. Only applicable to devices with more than one internal display. If this option is
set to false, DisplayManager will make additional effort to ensure no more than 1 internal
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5bb86dc..80bf795 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
<dimen name="notification_headerless_margin_twoline">20dp</dimen>
<!-- The height of each of the 1 or 2 lines in the headerless notification template -->
- <dimen name="notification_headerless_line_height">24dp</dimen>
+ <dimen name="notification_headerless_line_height">24sp</dimen>
<!-- vertical margin for the headerless notification content -->
<dimen name="notification_headerless_min_height">56dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3074906..6afdae5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -327,6 +327,7 @@
<item>@string/wfcSpnFormat_wifi_calling_wo_hyphen</item>
<item>@string/wfcSpnFormat_vowifi</item>
<item>@string/wfcSpnFormat_spn_wifi_calling_vo_hyphen</item>
+ <item>@string/wfcSpnFormat_wifi_call</item>
</string-array>
<!-- Spn during Wi-Fi Calling: "<operator>" -->
@@ -353,6 +354,8 @@
<string name="wfcSpnFormat_wifi_calling_wo_hyphen">WiFi Calling</string>
<!-- Spn during Wi-Fi Calling: "VoWifi" -->
<string name="wfcSpnFormat_vowifi">VoWifi</string>
+ <!-- Spn during Wi_Fi Calling: "WiFi Call" (without hyphen). This string is shown in the call banner for calls which take place over a WiFi network. -->
+ <string name="wfcSpnFormat_wifi_call">WiFi Call</string>
<!-- WFC, summary for Disabled -->
<string name="wifi_calling_off_summary">Off</string>
@@ -6264,6 +6267,12 @@
<string name="concurrent_display_notification_thermal_title">Device is too warm</string>
<!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+ <!-- Title of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_power_save_title">Dual Screen is unavailable</string>
+ <!-- Content of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_power_save_content">Dual Screen is unavailable because Battery Saver is on. You can turn this off in Settings.</string>
+ <!-- Text of power saver notification settings button. [CHAR LIMIT=NONE] -->
+ <string name="device_state_notification_settings_button">Go to Settings</string>
<!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
<string name="device_state_notification_turn_off_button">Turn off</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a15833d..1cb56e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1939,6 +1939,7 @@
<java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
<java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
<java-symbol type="bool" name="config_keepDreamingWhenUndocking" />
+ <java-symbol type="integer" name="config_keyguardDrawnTimeout" />
<java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
<java-symbol type="bool" name="config_wimaxEnabled" />
@@ -4930,12 +4931,17 @@
<java-symbol type="array" name="device_state_notification_active_contents"/>
<java-symbol type="array" name="device_state_notification_thermal_titles"/>
<java-symbol type="array" name="device_state_notification_thermal_contents"/>
+ <java-symbol type="array" name="device_state_notification_power_save_titles"/>
+ <java-symbol type="array" name="device_state_notification_power_save_contents"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
<java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
<java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+ <java-symbol type="string" name="concurrent_display_notification_power_save_title"/>
+ <java-symbol type="string" name="concurrent_display_notification_power_save_content"/>
<java-symbol type="string" name="device_state_notification_turn_off_button"/>
+ <java-symbol type="string" name="device_state_notification_settings_button"/>
<java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
<java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index b176307..6e1c580 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -23,7 +23,6 @@
import static junit.framework.Assert.fail;
-import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -78,7 +77,7 @@
mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
- mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+ mLogger.logBackupMetadata(DATA_TYPE_1, /* metadata */ "metadata");
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
}
@@ -91,7 +90,7 @@
String dataType = DATA_TYPE_1 + i;
mLogger.logItemsBackedUp(dataType, /* count */ 5);
mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
- mLogger.logBackupMetaData(dataType, METADATA_1);
+ mLogger.logBackupMetadata(dataType, METADATA_1);
assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
Optional.empty());
@@ -127,8 +126,8 @@
public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
mLogger = new BackupRestoreEventLogger(BACKUP);
- mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
- mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+ mLogger.logBackupMetadata(DATA_TYPE_1, METADATA_1);
+ mLogger.logBackupMetadata(DATA_TYPE_1, METADATA_2);
byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
byte[] expectedHash = getMetaDataHash(METADATA_2);
@@ -315,7 +314,7 @@
}
private static DataTypeResult getResultForDataType(
- BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+ BackupRestoreEventLogger logger, String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
@@ -324,7 +323,7 @@
}
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
- BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+ BackupRestoreEventLogger logger, String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
return resultList.stream()
.filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 1ec2613..fccb177 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -24,6 +24,7 @@
import static com.google.common.truth.Truth.assertThat;
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;
@@ -224,7 +225,7 @@
}
@Test
- public void onTouchEvent_startHandwriting_delegate() {
+ public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
View delegateView = new View(mContext);
delegateView.setIsHandwritingDelegate(true);
@@ -245,6 +246,29 @@
}
@Test
+ public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
+ View delegateView = new View(mContext);
+ delegateView.setIsHandwritingDelegate(true);
+ mHandwritingInitiator.onInputConnectionCreated(delegateView);
+ reset(mHandwritingInitiator);
+
+ mTestView1.setHandwritingDelegatorCallback(
+ () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
+ }
+
+ @Test
public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
final Rect rect = new Rect(600, 600, 900, 900);
final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index 35b3267..6189914 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.app.ActivityManager;
+
import androidx.test.filters.SmallTest;
import com.android.internal.util.FrameworkStatsLog;
@@ -128,6 +130,34 @@
}
@SmallTest
+ public void testDumpBoundFgsDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
public void testDumpProcessAssociation() throws Exception {
ProcessStats processStats = new ProcessStats();
AssociationState associationState =
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 2d6ae2e..19c52a6 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -20,6 +20,7 @@
<permission name="android.permission.CALL_PRIVILEGED"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<!-- Required to update emergency gesture settings -->
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e287867..0faf62e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -275,6 +275,7 @@
<!-- Permission required to test onPermissionsChangedListener -->
<permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.QUERY_USERS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
@@ -320,6 +321,7 @@
<permission name="android.permission.REGISTER_CONNECTION_MANAGER"/>
<permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
<permission name="android.permission.RETRIEVE_WINDOW_CONTENT"/>
+ <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
<permission name="android.permission.SET_ALWAYS_FINISH"/>
<permission name="android.permission.SET_ANIMATION_SCALE"/>
<permission name="android.permission.SET_DEBUG_APP"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ffc5ff2..5549f88 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,12 +259,6 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1878839956": {
- "message": "Marking app token %s with replacing windows.",
- "level": "DEBUG",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1872288685": {
"message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -463,12 +457,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/Task.java"
},
- "-1698815688": {
- "message": "Resetting app token %s of replacing window marks.",
- "level": "DEBUG",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1679411993": {
"message": "setVr2dDisplayId called for: %d",
"level": "DEBUG",
@@ -481,12 +469,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1661704580": {
- "message": "Attempted to set replacing window on non-existing app token %s",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-1647332198": {
"message": "remove RecentTask %s when finishing user %d",
"level": "INFO",
@@ -613,12 +595,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1515151503": {
- "message": ">>> OPEN TRANSACTION removeReplacedWindows",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/RootWindowContainer.java"
- },
"-1501564055": {
"message": "Organized TaskFragment is not ready= %s",
"level": "VERBOSE",
@@ -691,12 +667,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "-1471946192": {
- "message": "Marking app token %s with replacing child windows.",
- "level": "DEBUG",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1471518109": {
"message": "Set animatingExit: reason=onAppVisibilityChanged win=%s",
"level": "DEBUG",
@@ -919,12 +889,6 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
- "-1270731689": {
- "message": "Attempted to set replacing window on app token with no content %s",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-1263316010": {
"message": "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and oldRotation=%s (%d)",
"level": "VERBOSE",
@@ -1417,12 +1381,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
},
- "-799003045": {
- "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ANIM",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"-787664727": {
"message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s",
"level": "ERROR",
@@ -1453,6 +1411,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-778347463": {
+ "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mDisplayFrozen=%b callers=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"-775004869": {
"message": "Not a match: %s",
"level": "DEBUG",
@@ -1963,12 +1927,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-320419645": {
- "message": "Removing replaced window: %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"-319689203": {
"message": "Reparenting to original parent: %s for %s",
"level": "INFO",
@@ -2335,12 +2293,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "38267433": {
- "message": "Attempted to reset replacing window on non-existing app token %s",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"45285419": {
"message": "startingWindow was set but startingSurface==null, couldn't remove",
"level": "VERBOSE",
@@ -2989,12 +2941,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "594260654": {
- "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"600140673": {
"message": "checkBootAnimationComplete: Waiting for anim complete",
"level": "INFO",
@@ -3829,12 +3775,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1423592961": {
- "message": "<<< CLOSE TRANSACTION removeReplacedWindows",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/RootWindowContainer.java"
- },
"1430336882": {
"message": "findFocusedWindow: focusedApp windows not focusable using new focus @ %s",
"level": "VERBOSE",
@@ -3901,12 +3841,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
- "1515161239": {
- "message": "removeDeadWindows: %s",
- "level": "WARN",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1518495446": {
"message": "removeWindowToken: Attempted to remove non-existing token: %s",
"level": "WARN",
@@ -4273,12 +4207,6 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
- "1921821199": {
- "message": "Preserving %s until the new one is added",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"1928325128": {
"message": "Run showImeRunner",
"level": "DEBUG",
@@ -4489,12 +4417,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "2114149926": {
- "message": "Not removing %s because app died while it's visible",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"2117696413": {
"message": "moveTaskToFront: moving taskId=%d",
"level": "DEBUG",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 8e2a59c..e7ddc88 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -255,9 +255,12 @@
</family>
<family name="cursive">
- <font weight="400" style="normal" postScriptName="DancingScript">DancingScript-Regular.ttf
- </font>
- <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+ <font weight="400" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="700" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
</family>
<family name="sans-serif-smallcaps">
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index fe6eeeb..1048742 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -42,9 +42,10 @@
label: 'C'
base: 'c'
shift, capslock: 'C'
- alt: '\u00e7'
- shift+alt: '\u00c7'
shift+capslock: 'c'
+ alt: '\u00e7'
+ shift+alt, capslock+alt: '\u00c7'
+ shift+capslock+alt: '\u00e7'
}
key D {
@@ -58,8 +59,8 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
- alt: '\u0301'
shift+capslock: 'e'
+ alt: '\u0301'
}
key F {
@@ -87,8 +88,8 @@
label: 'I'
base: 'i'
shift, capslock: 'I'
- alt: '\u0302'
shift+capslock: 'i'
+ alt: '\u0302'
}
key J {
@@ -123,8 +124,8 @@
label: 'N'
base: 'n'
shift, capslock: 'N'
- alt: '\u0303'
shift+capslock: 'n'
+ alt: '\u0303'
}
key O {
@@ -159,8 +160,8 @@
label: 'S'
base: 's'
shift, capslock: 'S'
- alt: '\u00df'
shift+capslock: 's'
+ alt: '\u00df'
}
key T {
@@ -174,8 +175,8 @@
label: 'U'
base: 'u'
shift, capslock: 'U'
- alt: '\u0308'
shift+capslock: 'u'
+ alt: '\u0308'
}
key V {
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 100644
index 0000000..bfea4db
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1,34 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Apple Magic Trackpad 2 (Bluetooth) configuration file
+#
+# WHEN MODIFYING, also change the USB file (Vendor_05ac_Product_0265.idc)
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_03f6_Product_a001.idc b/data/keyboards/Vendor_03f6_Product_a001.idc
new file mode 100644
index 0000000..bcb4ee3
--- /dev/null
+++ b/data/keyboards/Vendor_03f6_Product_a001.idc
@@ -0,0 +1,22 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Brydge Touchpad
+#
+
+# Reports from this touchpad sometimes get bunched together due to Bluetooth
+# batching, leading to bad timestamps that mess up finger velocity calculations.
+# To fix this, set a fake delta using the touchpad's known report rate.
+gestureProp.Fake_Timestamp_Delta = 0.010
diff --git a/data/keyboards/Vendor_046d_Product_4011.idc b/data/keyboards/Vendor_046d_Product_4011.idc
new file mode 100644
index 0000000..3a23830
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4011.idc
@@ -0,0 +1,32 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Logitech Wireless Touchpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -313.240741792594
+gestureProp.Pressure_Calibration_Slope = 4.39678062436752
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Palm_Pressure = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4101.idc b/data/keyboards/Vendor_046d_Product_4101.idc
new file mode 100644
index 0000000..47e2530
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4101.idc
@@ -0,0 +1,31 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Logitech T650
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -0.439288351750068
+gestureProp.Pressure_Calibration_Slope = 3.05998553523335
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4102.idc b/data/keyboards/Vendor_046d_Product_4102.idc
new file mode 100644
index 0000000..e33a28a
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4102.idc
@@ -0,0 +1,24 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Logitech TK820
+#
+
+gestureProp.Touchpad_Stack_Version = 2
+# Pressure jumps around a lot on this touchpad, so allow that:
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Pressure_Calibration_Offset = -18.8078435
+gestureProp.Pressure_Calibration_Slope = 2.466208137
diff --git a/data/keyboards/Vendor_046d_Product_b00c.idc b/data/keyboards/Vendor_046d_Product_b00c.idc
new file mode 100644
index 0000000..a49970c
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_b00c.idc
@@ -0,0 +1,31 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Logitech T651
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -4.46520447177073
+gestureProp.Pressure_Calibration_Slope = 3.21071719332644
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
new file mode 100644
index 0000000..520d188
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -0,0 +1,34 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Apple Magic Trackpad 2 (USB) configuration file
+#
+# WHEN MODIFYING, also change the Bluetooth file (Vendor_004c_Product_0265.idc)
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_030e.idc b/data/keyboards/Vendor_05ac_Product_030e.idc
new file mode 100644
index 0000000..23a2e18
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_030e.idc
@@ -0,0 +1,38 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Apple Magic Trackpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+# We are using raw touch major value as pressure value, so set the Palm
+# pressure threshold high.
+gestureProp.Palm_Pressure = 1000
+gestureProp.Compute_Surface_Area_from_Pressure = 0
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+# NOTE: bias on X-axis is uncalibrated
+gestureProp.Touchpad_Device_Output_Bias_on_X-Axis = -283.3226025266607
+gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis = -283.3226025266607
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+# Drumroll suppression causes janky movement on this touchpad.
+gestureProp.Drumroll_Suppression_Enable = 0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index 53308e3..06b8237 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -39,9 +39,10 @@
label: 'C'
base: 'c'
shift, capslock: 'C'
- alt: '\u00e7'
- shift+alt: '\u00c7'
shift+capslock: 'c'
+ alt: '\u00e7'
+ shift+alt, capslock+alt: '\u00c7'
+ shift+capslock+alt: '\u00e7'
}
key D {
@@ -55,8 +56,8 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
- alt: '\u0301'
shift+capslock: 'e'
+ alt: '\u0301'
}
key F {
@@ -84,8 +85,8 @@
label: 'I'
base: 'i'
shift, capslock: 'I'
- alt: '\u0302'
shift+capslock: 'i'
+ alt: '\u0302'
}
key J {
@@ -120,8 +121,8 @@
label: 'N'
base: 'n'
shift, capslock: 'N'
- alt: '\u0303'
shift+capslock: 'n'
+ alt: '\u0303'
}
key O {
@@ -156,8 +157,8 @@
label: 'S'
base: 's'
shift, capslock: 'S'
- alt: '\u00df'
shift+capslock: 's'
+ alt: '\u00df'
}
key T {
@@ -171,8 +172,8 @@
label: 'U'
base: 'u'
shift, capslock: 'U'
- alt: '\u0308'
shift+capslock: 'u'
+ alt: '\u0308'
}
key V {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c1f6c29..c3b0f9b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -808,9 +808,12 @@
KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
platformReportedBrand.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedDevice =
+ isPropertyEmptyOrUnknown(Build.DEVICE_FOR_ATTESTATION)
+ ? Build.DEVICE : Build.DEVICE_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
- Build.DEVICE.getBytes(StandardCharsets.UTF_8)
+ platformReportedDevice.getBytes(StandardCharsets.UTF_8)
));
final String platformReportedProduct =
isPropertyEmptyOrUnknown(Build.PRODUCT_FOR_ATTESTATION)
@@ -819,9 +822,12 @@
KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
platformReportedProduct.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedManufacturer =
+ isPropertyEmptyOrUnknown(Build.MANUFACTURER_FOR_ATTESTATION)
+ ? Build.MANUFACTURER : Build.MANUFACTURER_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
- Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
+ platformReportedManufacturer.getBytes(StandardCharsets.UTF_8)
));
final String platformReportedModel =
isPropertyEmptyOrUnknown(Build.MODEL_FOR_ATTESTATION)
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4b12590..852edef 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 298ad30..8d1da0f7 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -63,11 +63,11 @@
android:tint="@color/bubbles_icon_tint"/>
<TextView
+ android:id="@+id/bubble_manage_menu_dont_bubble_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
- android:text="@string/bubbles_dont_bubble_conversation" />
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 3082962..9f6cf79 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,6 +146,8 @@
<string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
<string name="bubble_dismiss_text">Dismiss bubble</string>
+ <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]-->
+ <string name="bubbles_dont_bubble">Don\u2019t bubble</string>
<!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
<string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
<!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index ef53839..48fe65d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1028,19 +1028,21 @@
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
*/
- public void showOrHideAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) {
Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
return;
}
- PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+ PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
@@ -1061,7 +1063,7 @@
}
} else {
// App bubble does not exist, lets add and expand it
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ Bubble b = new Bubble(intent, user, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1869,10 +1871,9 @@
}
@Override
- public void showOrHideAppBubble(Intent intent) {
- mMainExecutor.execute(() -> {
- BubbleController.this.showOrHideAppBubble(intent);
- });
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.showOrHideAppBubble(intent, user));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index ecddbda..e5a4362 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -236,12 +236,17 @@
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
if (mBubble.isAppBubble()) {
- PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ PendingIntent pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
mBubble.getAppBubbleIntent()
.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
PendingIntent.FLAG_IMMUTABLE,
- null);
+ /* options= */ null);
mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 0b947c8..deb4fd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -844,6 +844,8 @@
private DismissView mDismissView;
private ViewGroup mManageMenu;
+ private TextView mManageDontBubbleText;
+ private ViewGroup mManageSettingsView;
private ImageView mManageSettingsIcon;
private TextView mManageSettingsText;
private boolean mShowingManage = false;
@@ -1217,7 +1219,11 @@
mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
});
- mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+ mManageDontBubbleText = mManageMenu
+ .findViewById(R.id.bubble_manage_menu_dont_bubble_text);
+
+ mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container);
+ mManageSettingsView.setOnClickListener(
view -> {
showManageMenu(false /* show */);
final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
@@ -2868,10 +2874,19 @@
// name and icon.
if (show) {
final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
- if (bubble != null) {
+ if (bubble != null && !bubble.isAppBubble()) {
+ // Setup options for non app bubbles
+ mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation);
mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
mManageSettingsText.setText(getResources().getString(
R.string.bubbles_app_settings, bubble.getAppName()));
+ mManageSettingsView.setVisibility(VISIBLE);
+ } else {
+ // Setup options for app bubbles
+ mManageDontBubbleText.setText(R.string.bubbles_dont_bubble);
+ // App bubbles are not notification based
+ // so we don't show the option to go to notification settings
+ mManageSettingsView.setVisibility(GONE);
}
}
@@ -2936,6 +2951,15 @@
}
}
+ /**
+ * Checks whether manage menu notification settings action is available and visible
+ * Used for testing
+ */
+ @VisibleForTesting
+ public boolean isManageMenuSettingsVisible() {
+ return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE;
+ }
+
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 4c0a93f..5555bec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -129,12 +129,14 @@
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
*/
- void showOrHideAppBubble(Intent intent);
+ void showOrHideAppBubble(Intent intent, UserHandle user);
/** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
boolean isAppBubbleTaskId(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index bf226283..cb1a6e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -22,10 +22,12 @@
import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Configuration;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Surface;
@@ -34,6 +36,8 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -49,8 +53,34 @@
public class TabletopModeController implements
DevicePostureController.OnDevicePostureChangedListener,
DisplayController.OnDisplaysChangedListener {
+ /**
+ * When {@code true}, floating windows like PiP would auto move to the position
+ * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+ */
+ private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+
+ /**
+ * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+ * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+ * See also {@link #getPreferredHalfInTabletopMode()}.
+ */
+ private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+ SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+ @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+ PREFERRED_TABLETOP_HALF_TOP,
+ PREFERRED_TABLETOP_HALF_BOTTOM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreferredTabletopHalf {}
+
+ public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+ public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
private final Context mContext;
private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@
}
}
+ /**
+ * @return {@code true} if floating windows like PiP would auto move to the position
+ * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+ */
+ public boolean enableMoveFloatingWindowInTabletop() {
+ return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+ }
+
+ /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+ @PreferredTabletopHalf
+ public int getPreferredHalfInTabletopMode() {
+ return PREFER_TOP_HALF_IN_TABLETOP
+ ? PREFERRED_TABLETOP_HALF_TOP
+ : PREFERRED_TABLETOP_HALF_BOTTOM;
+ }
+
/** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
public void registerOnTabletopModeChangedListener(
@NonNull OnTabletopModeChangedListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ba0f073..7a83d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -45,6 +45,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -357,6 +358,7 @@
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
@@ -366,7 +368,7 @@
pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor));
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73a7403..31c5e33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.os.IBinder
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -32,6 +33,7 @@
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
@@ -115,10 +117,7 @@
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(wct)
-
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
- wct.reorder(task.getToken(), true /* onTop */)
-
+ addMoveToDesktopChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -136,8 +135,7 @@
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(task.getToken(), null)
+ addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -234,8 +232,8 @@
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToDesktopChanges(wct, task.token)
}
}
}
@@ -251,15 +249,44 @@
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
- setBounds(task.token, null)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task.token)
}
}
}
return null
}
+ private fun addMoveToDesktopChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+ wct.reorder(token, true /* onTop */)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getDesktopDensityDpi())
+ }
+ }
+
+ private fun addMoveToFullscreenChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(token, null)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getFullscreenDensityDpi())
+ }
+ }
+
+ private fun getFullscreenDensityDpi(): Int {
+ return context.resources.displayMetrics.densityDpi
+ }
+
+ private fun getDesktopDensityDpi(): Int {
+ return DESKTOP_DENSITY_OVERRIDE
+ }
+
/** Creates a new instance of the external interface to pass to another process. */
private fun createExternalInterface(): ExternalInterfaceBinder {
return IDesktopModeImpl(this)
@@ -318,4 +345,18 @@
return result[0]
}
}
+
+ companion object {
+ private val DESKTOP_DENSITY_OVERRIDE =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+ private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+ /**
+ * Check if desktop density override is enabled
+ */
+ @JvmStatic
+ fun isDesktopDensityOverrideSet(): Boolean {
+ return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index d094c22..998728d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -129,6 +129,7 @@
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+ boolean oldVisible = state.mTaskInfo.isVisible;
if (mWindowDecorViewModelOptional.isPresent()) {
mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
@@ -138,12 +139,18 @@
updateRecentsForVisibleFullscreenTask(taskInfo);
final Point positionInParent = state.mTaskInfo.positionInParent;
- if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+ boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
+ boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
+
+ if (becameVisible || positionInParentChanged) {
mSyncQueue.runInSync(t -> {
if (!state.mLeash.isValid()) {
// Task vanished before sync completion
return;
}
+ if (becameVisible) {
+ t.show(state.mLeash);
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index f664808..f08742d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -43,7 +43,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -113,6 +115,12 @@
* @see android.view.View#setPreferKeepClearRects
*/
private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+ /**
+ * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+ * as unrestricted keep clear area. Values in this map would be appended to
+ * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+ */
+ private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -378,6 +386,16 @@
mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
}
+ /** Add a named unrestricted keep clear area. */
+ public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+ mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+ }
+
+ /** Remove a named unrestricted keep clear area. */
+ public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+ mNamedUnrestrictedKeepClearAreas.remove(name);
+ }
+
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
@@ -385,7 +403,10 @@
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
- return mUnrestrictedKeepClearAreas;
+ if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+ final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+ unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+ return unrestrictedAreas;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87cf655..45bb73b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -489,10 +489,11 @@
// Reparent the pip leash to the root with max layer so that we can animate it outside of
// parent crop, and make sure it is not covered by other windows.
final SurfaceControl pipLeash = pipChange.getLeash();
- startTransaction.reparent(pipLeash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+ startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
// Note: because of this, the bounds to animate should be translated to the root coordinate.
- final Point offset = info.getRootOffset();
+ final Point offset = info.getRoot(rootIdx).getOffset();
final Rect currentBounds = mPipBoundsState.getBounds();
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
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 db6ef1d..ea2559a 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
@@ -43,6 +43,7 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -71,6 +72,7 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -102,7 +104,6 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -117,6 +118,8 @@
UserChangeListener {
private static final String TAG = "PipController";
+ private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
@@ -147,6 +150,7 @@
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private DisplayInsetsController mDisplayInsetsController;
+ private TabletopModeController mTabletopModeController;
private Optional<OneHandedController> mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
@@ -403,6 +407,7 @@
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -417,7 +422,7 @@
pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor)
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
.mImpl;
}
@@ -444,6 +449,7 @@
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController tabletopModeController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
@@ -477,6 +483,7 @@
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
mDisplayInsetsController = displayInsetsController;
+ mTabletopModeController = tabletopModeController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -659,6 +666,42 @@
}
});
+ mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+ if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+ final String tag = "tabletop-mode";
+ if (!isInTabletopMode) {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+ return;
+ }
+
+ // To prepare for the entry bounds.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ if (mTabletopModeController.getPreferredHalfInTabletopMode()
+ == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+ // Prefer top, avoid the bottom half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.centerY(),
+ displayBounds.right, displayBounds.bottom));
+ } else {
+ // Prefer bottom, avoid the top half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.top,
+ displayBounds.right, displayBounds.centerY()));
+ }
+
+ // Try to move the PiP window if we have entered PiP mode.
+ if (mPipTransitionState.hasEnteredPip()) {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+ // PiP bounds is too big to fit either half, bail early.
+ return;
+ }
+ mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+ }
+ });
+
mOneHandedController.ifPresent(controller -> {
controller.registerTransitionCallback(
new OneHandedTransitionCallback() {
@@ -892,12 +935,10 @@
0, mPipBoundsState.getDisplayBounds().bottom - height,
mPipBoundsState.getDisplayBounds().right,
mPipBoundsState.getDisplayBounds().bottom);
- Set<Rect> restrictedKeepClearAreas = new HashSet<>(
- mPipBoundsState.getRestrictedKeepClearAreas());
- restrictedKeepClearAreas.add(rect);
- mPipBoundsState.setKeepClearAreas(restrictedKeepClearAreas,
- mPipBoundsState.getUnrestrictedKeepClearAreas());
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
updatePipPositionForKeepClearAreas();
+ } else {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index e1c0895..e09c3c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -37,6 +37,7 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -128,6 +129,7 @@
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -135,7 +137,7 @@
t.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
t.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -145,8 +147,9 @@
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ final Point rootOffset = info.getRoot(rootIdx).getOffset();
+ startBounds.offset(-rootOffset.x, -rootOffset.y);
+ endBounds.offset(-rootOffset.x, -rootOffset.y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
boolean isRootOrSplitSideRoot = change.getParent() == null
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 a5546e5..a1eaf85 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
@@ -259,37 +259,6 @@
}
};
- private final SplitScreenTransitions.TransitionFinishedCallback
- mRecentTransitionFinishedCallback =
- new SplitScreenTransitions.TransitionFinishedCallback() {
- @Override
- public void onFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current
- // split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT,
- false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
-
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
-
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
@@ -388,6 +357,11 @@
return mMainStage.isActive();
}
+ /** Checks if `transition` is a pending enter-split transition. */
+ public boolean isPendingEnter(IBinder transition) {
+ return mSplitTransitions.isPendingEnter(transition);
+ }
+
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -2264,11 +2238,16 @@
}
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME
- || activityType == ACTIVITY_TYPE_RECENTS) {
- // Enter overview panel, so start recent transition.
- mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionFinishedCallback);
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ if (request.getRemoteTransition() != null) {
+ // starting recents/home, so don't handle this and let it fall-through to
+ // the remote handler.
+ return null;
+ }
+ // Need to use the old stuff for non-remote animations, otherwise we don't
+ // exit split-screen.
+ mSplitTransitions.setRecentTransition(transition, null /* remote */,
+ this::onRecentsInSplitAnimationFinish);
}
}
} else {
@@ -2398,7 +2377,7 @@
shouldAnimate = startPendingEnterAnimation(
transition, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingRecent(transition)) {
- shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
+ onRecentsInSplitAnimationStart(startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2653,10 +2632,35 @@
return true;
}
- private boolean startPendingRecentAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ /** Call this when starting the open-recents animation while split-screen is active. */
+ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
setDividerVisibility(false, t);
- return true;
+ }
+
+ /** Call this when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 75112b6..d094892 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -25,6 +26,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -68,14 +70,20 @@
/** Pip was entered while handling an intent with its own remoteTransition. */
static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
+ /** Recents transition while split-screen active. */
+ static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
/** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
static final int ANIM_TYPE_GOING_HOME = 1;
+ /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+ static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
final int mType;
- int mAnimType = 0;
+ int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
Transitions.TransitionHandler mLeftoversHandler = null;
@@ -167,6 +175,27 @@
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
+ } else if (mSplitHandler.isSplitActive()
+ && isOpeningType(request.getType())
+ && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
+ || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)
+ && request.getRemoteTransition() != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+ mPlayer.dispatchRequest(transition, request, this);
+ if (handler == null) {
+ android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected"
+ + ", there should at-least be RemoteHandler.");
+ return null;
+ }
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = handler.first;
+ mActiveTransitions.add(mixed);
+ return handler.second;
}
return null;
}
@@ -179,7 +208,9 @@
out.getChanges().add(info.getChanges().get(i));
}
}
- out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ out.addRoot(info.getRoot(i));
+ }
out.setAnimationOptions(info.getAnimationOptions());
return out;
}
@@ -214,6 +245,9 @@
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
finishTransaction, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -439,12 +473,40 @@
return true;
}
+ private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations = 0;
+ mActiveTransitions.remove(mixed);
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct, wctCB);
+ };
+ mixed.mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+ final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mActiveTransitions.remove(mixed);
+ }
+ return handled;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
- if (mActiveTransitions.get(i) != mergeTarget) continue;
+ if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
@@ -472,6 +534,14 @@
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
}
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -491,6 +561,8 @@
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f66c26b..63c7969 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -383,9 +383,10 @@
continue;
}
// No default animation for this, so just update bounds/position.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - info.getRootOffset().x,
- change.getEndAbsBounds().top - info.getRootOffset().y);
+ change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -474,8 +475,10 @@
}
if (backgroundColorForTransition != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
- startTransaction, finishTransaction);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+ startTransaction, finishTransaction);
+ }
}
if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +523,10 @@
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+ mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+ animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 27b82c0..039bde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Arrays;
@@ -415,8 +416,8 @@
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
- if (info.getRootLeash().isValid()) {
- t.show(info.getRootLeash());
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
@@ -434,10 +435,12 @@
boolean hasParent = change.getParent() != null;
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (!hasParent) {
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ t.setPosition(leash,
+ change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
@@ -532,12 +535,6 @@
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
- if (!info.getRootLeash().isValid()) {
- // Shell has some debug settings which makes calling binders with invalid
- // surfaces crash, so replace it with a "real" one.
- info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
- .setContainerLayer().build(), 0, 0);
- }
// Sleep starts a process of forcing all prior transitions to finish immediately
finishForSleep(null /* forceFinish */);
return;
@@ -546,10 +543,10 @@
// Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
// screen-off, so there might no visibility change involved.
- if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
- // Invalid root-leash implies that the transition is empty/no-op, so just do
+ if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
+ // No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
transitionToken, info);
onAbort(active);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 8c6e1e7..7595c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -139,11 +139,12 @@
// changes should be ordered top-to-bottom in z
final int mode = change.getMode();
- t.reparent(leash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
final Rect absBounds =
(mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
- t.setPosition(leash, absBounds.left - info.getRootOffset().x,
- absBounds.top - info.getRootOffset().y);
+ t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+ absBounds.top - info.getRoot(rootIdx).getOffset().y);
// Put all the OPEN/SHOW on top
if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +180,13 @@
// making leashes means we have to handle them specially.
return change.getLeash();
}
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName(change.getLeash().toString() + "_transition-leash")
.setContainerLayer()
// Initial the surface visible to respect the visibility of the original surface.
.setHidden(false)
- .setParent(info.getRootLeash())
+ .setParent(info.getRoot(rootIdx).getLeash())
.build();
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +263,18 @@
target.setRotationChange(change.getEndRotation() - change.getStartRotation());
return target;
}
+
+ /**
+ * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+ * the start display. If there is no display, it will fallback on the 0th root in the
+ * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+ */
+ public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo info) {
+ int rootIdx = info.findRootIndex(change.getEndDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ rootIdx = info.findRootIndex(change.getStartDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ return 0;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 72da108..3c0ef96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
@@ -46,6 +47,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -95,6 +97,17 @@
mDesktopActive = DesktopModeStatus.isActive(mContext);
}
+ @Override
+ protected Configuration getConfigurationWithOverrides(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ Configuration configuration = taskInfo.getConfiguration();
+ if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+ // Density is overridden for desktop tasks. Keep system density for window decoration.
+ configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
+ }
+ return configuration;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
new file mode 100644
index 0000000..4417209
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
@@ -0,0 +1 @@
+jorgegil@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7a7ac47..ddd3b44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -131,7 +131,17 @@
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+ mDecorWindowContext = mContext.createConfigurationContext(
+ getConfigurationWithOverrides(mTaskInfo));
+ }
+
+ /**
+ * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+ *
+ * Allows values to be overridden before returning the configuration.
+ */
+ protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+ return taskInfo.getConfiguration();
}
/**
@@ -165,7 +175,7 @@
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = mTaskInfo.getConfiguration();
+ final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
index 35c374d..26b787f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -30,6 +30,7 @@
*/
public class TransitionInfoBuilder {
final TransitionInfo mInfo;
+ static final int DISPLAY_ID = 0;
public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
this(type, 0 /* flags */);
@@ -38,7 +39,7 @@
public TransitionInfoBuilder(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
mInfo = new TransitionInfo(type, flags);
- mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+ mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +62,7 @@
}
public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
mInfo.addChange(change);
return this;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0e14c69..108e273 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -53,6 +53,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
@@ -115,6 +116,7 @@
@Mock private Optional<OneHandedController> mMockOneHandedController;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock private TabletopModeController mMockTabletopModeController;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -137,7 +139,8 @@
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+ mMockDisplayInsetsController, mMockTabletopModeController,
+ mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -228,7 +231,8 @@
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+ mMockDisplayInsetsController, mMockTabletopModeController,
+ mMockOneHandedController, mMockExecutor));
}
@Test
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 3901dab..df78d92 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
@@ -35,6 +35,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -249,7 +250,7 @@
@Test
@UiThreadTest
- public void testEnterRecents() {
+ public void testEnterRecentsAndCommit() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -258,27 +259,65 @@
.build();
// Create a request to bring home forward
- TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
- assertTrue(result.isEmpty());
+ // Don't handle recents opening
+ assertNull(result);
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
- // simulate the transition
- TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
- .addChange(TRANSIT_TO_FRONT, homeTask)
- .addChange(TRANSIT_TO_BACK, mMainChild)
- .addChange(TRANSIT_TO_BACK, mSideChild)
- .build();
+ // simulate the start of recents transition
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
- mStageCoordinator.startAnimation(transition, info,
- mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class),
- mock(Transitions.TransitionFinishCallback.class));
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure it cleans-up if recents doesn't restore
+ WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+ mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mock(SurfaceControl.Transaction.class));
+ assertFalse(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnterRecentsAndRestore() {
+ enterSplit();
+
+ ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .build();
+
+ // Create a request to bring home forward
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
+ IBinder transition = mock(IBinder.class);
+ WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+ // Don't handle recents opening
+ assertNull(result);
+
+ // make sure we haven't made any local changes yet (need to wait until transition is ready)
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // simulate the start of recents transition
+ mMainStage.onTaskVanished(mMainChild);
+ mSideStage.onTaskVanished(mSideChild);
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure we remain in split after recents restores.
+ WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+ restoreWCT.reorder(mMainChild.token, true /* toTop */);
+ restoreWCT.reorder(mSideChild.token, true /* toTop */);
+ // simulate the restoreWCT being applied:
+ mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+ mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+ mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mock(SurfaceControl.Transaction.class));
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b0896da..9df6822 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -91,6 +91,8 @@
bool Properties::isLowRam = false;
bool Properties::isSystemOrPersistent = false;
+float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -150,6 +152,11 @@
enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
+ auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str());
+ if (hdrHeadroom >= 1.f) {
+ maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f);
+ }
+
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ed7175e..24e206b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -218,6 +218,8 @@
#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -321,6 +323,8 @@
static bool isLowRam;
static bool isSystemOrPersistent;
+ static float maxHdrHeadroomOn8bit;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 8977d3c..bfe4eaf 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -23,21 +23,55 @@
#include "utils/Trace.h"
#ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
#include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
#endif
namespace android::uirenderer {
using namespace renderthread;
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+ // We should always have a known destination colorspace. If we don't we must be in some
+ // legacy mode where we're lost and also definitely not going to HDR
+ if (destColorspace == nullptr) {
+ return 1.f;
+ }
+
+ constexpr float GenericSdrWhiteNits = 203.f;
+ constexpr float maxPQLux = 10000.f;
+ constexpr float maxHLGLux = 1000.f;
+ skcms_TransferFunction destTF;
+ destColorspace->transferFn(&destTF);
+ if (skcms_TransferFunction_isPQish(&destTF)) {
+ return maxPQLux / GenericSdrWhiteNits;
+ } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+ return maxHLGLux / GenericSdrWhiteNits;
+ } else {
+#ifdef __ANDROID__
+ CanvasContext* context = CanvasContext::getActiveContext();
+ return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+ return 1.f;
+#endif
+ }
+}
+
void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
ATRACE_CALL();
#ifdef __ANDROID__
- CanvasContext* context = CanvasContext::getActiveContext();
- float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
+ auto destColorspace = c->imageInfo().refColorSpace();
+ float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
if (targetSdrHdrRatio > 1.f && gainmapImage) {
SkPaint gainmapPaint = *paint;
float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@
gainmapSrc.fRight *= sX;
gainmapSrc.fTop *= sY;
gainmapSrc.fBottom *= sY;
- auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
- sampling, gainmapInfo, dst, targetSdrHdrRatio,
- c->imageInfo().refColorSpace());
+ auto shader =
+ SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+ gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
gainmapPaint.setShader(shader);
c->drawRect(dst, gainmapPaint);
} else
@@ -58,4 +92,213 @@
c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
}
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+ uniform shader base;
+ uniform shader gainmap;
+ uniform colorFilter workingSpaceToLinearSrgb;
+ uniform half4 logRatioMin;
+ uniform half4 logRatioMax;
+ uniform half4 gainmapGamma;
+ uniform half4 epsilonSdr;
+ uniform half4 epsilonHdr;
+ uniform half W;
+ uniform int gainmapIsAlpha;
+ uniform int gainmapIsRed;
+ uniform int singleChannel;
+ uniform int noGamma;
+
+ half4 toDest(half4 working) {
+ half4 ls = workingSpaceToLinearSrgb.eval(working);
+ vec3 dest = fromLinearSrgb(ls.rgb);
+ return half4(dest.r, dest.g, dest.b, ls.a);
+ }
+
+ half4 main(float2 coord) {
+ half4 S = base.eval(coord);
+ half4 G = gainmap.eval(coord);
+ if (gainmapIsAlpha == 1) {
+ G = half4(G.a, G.a, G.a, 1.0);
+ }
+ if (gainmapIsRed == 1) {
+ G = half4(G.r, G.r, G.r, 1.0);
+ }
+ if (singleChannel == 1) {
+ half L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.r, logRatioMax.r, G.r);
+ } else {
+ L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ } else {
+ half3 L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+ } else {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ }
+ }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+ static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+ auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+ if (buildResult.effect) {
+ return buildResult.effect.release();
+ } else {
+ LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+ }
+ }();
+ SkASSERT(effect);
+ return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+ return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+ sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+ SkRuntimeShaderBuilder mBuilder{mShader};
+ SkGainmapInfo mGainmapInfo;
+ std::mutex mUniformGuard;
+
+ void setupChildren(const sk_sp<const SkImage>& baseImage,
+ const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+ sk_sp<SkColorSpace> baseColorSpace =
+ baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+ // Determine the color space in which the gainmap math is to be applied.
+ sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+ // Create a color filter to transform from the base image's color space to the color space
+ // in which the gainmap is to be applied.
+ auto colorXformSdrToGainmap =
+ SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+ // The base image shader will convert into the color space in which the gainmap is applied.
+ auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+ ->makeWithColorFilter(colorXformSdrToGainmap);
+
+ // The gainmap image shader will ignore any color space that the gainmap has.
+ const SkMatrix gainmapRectToDstRect =
+ SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+ SkRect::MakeWH(baseImage->width(), baseImage->height()));
+ auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+ &gainmapRectToDstRect);
+
+ // Create a color filter to transform from the color space in which the gainmap is applied
+ // to the intermediate destination color space.
+ auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+ gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+ mBuilder.child("base") = std::move(baseImageShader);
+ mBuilder.child("gainmap") = std::move(gainmapImageShader);
+ mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+ }
+
+ void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo) {
+ const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+ const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+ const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+ gainmapInfo.fGainmapGamma.fG == 1.f &&
+ gainmapInfo.fGainmapGamma.fB == 1.f;
+ const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+ const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+ const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+ const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+ (colorTypeFlags == kGray_SkColorChannelFlag ||
+ colorTypeFlags == kAlpha_SkColorChannelFlag ||
+ colorTypeFlags == kRed_SkColorChannelFlag);
+ mBuilder.uniform("logRatioMin") = logRatioMin;
+ mBuilder.uniform("logRatioMax") = logRatioMax;
+ mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+ mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+ mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+ mBuilder.uniform("noGamma") = noGamma;
+ mBuilder.uniform("singleChannel") = singleChannel;
+ mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+ mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+ }
+
+ sk_sp<const SkData> build(float targetHdrSdrRatio) {
+ sk_sp<const SkData> uniforms;
+ {
+ // If we are called concurrently from multiple threads, we need to guard the call
+ // to writableUniforms() which mutates mUniform. This is otherwise safe because
+ // writeableUniforms() will make a copy if it's not unique before mutating
+ // This can happen if a BitmapShader is used on multiple canvas', such as a
+ // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+ std::lock_guard _lock(mUniformGuard);
+ const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ mBuilder.uniform("W") = W;
+ uniforms = mBuilder.uniforms();
+ }
+ return uniforms;
+ }
+
+public:
+ explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ mGainmapInfo = gainmapInfo;
+ setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+ setupGenericUniforms(gainmapImage, gainmapInfo);
+ }
+
+ static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+ image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+ auto callback =
+ [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+ -> sk_sp<const SkData> {
+ return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+ };
+ return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+ deferredHandler->mBuilder.children());
+ }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+ sampling);
+}
+
+#else // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return nullptr;
+}
+
+#endif // __ANDROID__
+
} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 7c56d94..4ed2445 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -30,4 +30,9 @@
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
} // namespace android::uirenderer
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 75d45e5..7eb79be 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,9 @@
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"
+#include <vector>
+
+#include "Gainmap.h"
#include "GraphicsJNI.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
@@ -17,10 +20,9 @@
#include "SkShader.h"
#include "SkString.h"
#include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
#include "include/effects/SkRuntimeEffect.h"
-#include <vector>
-
using namespace android::uirenderer;
/**
@@ -74,7 +76,20 @@
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
- image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+ auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+ image = bitmap.makeImage();
+
+ if (!isDirectSampled && bitmap.hasGainmap()) {
+ sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (gainmapShader) {
+ if (matrix) {
+ gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+ }
+ return reinterpret_cast<jlong>(gainmapShader.release());
+ }
+ }
}
if (!image.get()) {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f10b2b2..dd781bb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -311,7 +311,7 @@
}
switch (mColorMode) {
case ColorMode::Hdr:
- return 3.f; // TODO: Refine this number
+ return Properties::maxHdrHeadroomOn8bit;
case ColorMode::Hdr10:
return 10.f;
default:
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7a7f1ab..0afd949 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -135,7 +135,9 @@
!mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
- nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+
+ nsecs_t timeUntilDeadline = frameDeadline - frameTimeNanos;
+ nsecs_t runAt = (frameTimeNanos + (timeUntilDeadline * 0.25f));
queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
}
}
@@ -257,7 +259,6 @@
void RenderThread::setupFrameInterval() {
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
mTimeLord.setFrameInterval(frameIntervalNanos);
- mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}
void RenderThread::requireGlContext() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 0a89e5e..c77cd41 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -235,7 +235,6 @@
bool mFrameCallbackTaskPending;
TimeLord mTimeLord;
- nsecs_t mDispatchFrameDelay = 4_ms;
RenderState* mRenderState;
EglManager* mEglManager;
WebViewFunctorManager& mFunctorManager;
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 24cfc9d..c398405 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -63,24 +63,23 @@
mLocked.pointerSprite.clear();
}
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
std::scoped_lock lock(mLock);
- return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+ return getBoundsLocked();
}
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
if (!mLocked.viewport.isValid()) {
- return false;
+ return {};
}
- *outMinX = mLocked.viewport.logicalLeft;
- *outMinY = mLocked.viewport.logicalTop;
- *outMaxX = mLocked.viewport.logicalRight - 1;
- *outMaxY = mLocked.viewport.logicalBottom - 1;
- return true;
+ return FloatRect{
+ static_cast<float>(mLocked.viewport.logicalLeft),
+ static_cast<float>(mLocked.viewport.logicalTop),
+ static_cast<float>(mLocked.viewport.logicalRight - 1),
+ static_cast<float>(mLocked.viewport.logicalBottom - 1),
+ };
}
void MouseCursorController::move(float deltaX, float deltaY) {
@@ -121,31 +120,19 @@
}
void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- if (x <= minX) {
- mLocked.pointerX = minX;
- } else if (x >= maxX) {
- mLocked.pointerX = maxX;
- } else {
- mLocked.pointerX = x;
- }
- if (y <= minY) {
- mLocked.pointerY = minY;
- } else if (y >= maxY) {
- mLocked.pointerY = maxY;
- } else {
- mLocked.pointerY = y;
- }
- updatePointerLocked();
- }
+ const auto bounds = getBoundsLocked();
+ if (!bounds) return;
+
+ mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+ mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+ updatePointerLocked();
}
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
std::scoped_lock lock(mLock);
- *outX = mLocked.pointerX;
- *outY = mLocked.pointerY;
+ return {mLocked.pointerX, mLocked.pointerY};
}
int32_t MouseCursorController::getDisplayId() const {
@@ -235,10 +222,9 @@
// Reset cursor position to center if size or display changed.
if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
oldDisplayHeight != newDisplayHeight) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- mLocked.pointerX = (minX + maxX) * 0.5f;
- mLocked.pointerY = (minY + maxY) * 0.5f;
+ if (const auto bounds = getBoundsLocked(); bounds) {
+ mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+ mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index db0ab56..26be2a8 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,12 +43,12 @@
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBounds() const;
void move(float deltaX, float deltaY);
void setButtonState(int32_t buttonState);
int32_t getButtonState() const;
void setPosition(float x, float y);
- void getPosition(float* outX, float* outY) const;
+ FloatPoint getPosition() const;
int32_t getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
@@ -102,7 +102,7 @@
} mLocked GUARDED_BY(mLock);
- bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBoundsLocked() const;
void setPositionLocked(float x, float y);
void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index fedf58d..544edc2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -114,16 +114,15 @@
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
- mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
}
std::mutex& PointerController::getLock() const {
return mDisplayInfoListener->mLock;
}
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
- return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+ return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
@@ -156,15 +155,13 @@
mCursorController.setPosition(transformed.x, transformed.y);
}
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
const int32_t displayId = mCursorController.getDisplayId();
- mCursorController.getPosition(outX, outY);
+ const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
const auto& transform = getTransformForDisplayLocked(displayId);
- const auto xy = transform.inverse().transform(*outX, *outY);
- *outX = xy.x;
- *outY = xy.y;
+ return FloatPoint{transform.inverse().transform(p.x, p.y)};
}
}
@@ -262,19 +259,31 @@
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- std::scoped_lock lock(getLock());
+ struct PointerDisplayChangeArgs {
+ int32_t displayId;
+ FloatPoint cursorPosition;
+ };
+ std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
- bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER ||
- mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
- 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;
+ { // acquire lock
+ std::scoped_lock lock(getLock());
+
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+ getAdditionalMouseResources = true;
+ }
+ mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ mLocked.pointerDisplayId = viewport.displayId;
+ pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+ }
+ } // release lock
+
+ if (pointerDisplayChanged) {
+ // Notify the policy without holding the pointer controller lock.
+ mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+ pointerDisplayChanged->cursorPosition);
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 48d5a57..6d3557c 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,12 +50,12 @@
~PointerController() override;
- virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ virtual std::optional<FloatRect> getBounds() const;
virtual void move(float deltaX, float deltaY);
virtual void setButtonState(int32_t buttonState);
virtual int32_t getButtonState() const;
virtual void setPosition(float x, float y);
- virtual void getPosition(float* outX, float* outY) const;
+ virtual FloatPoint getPosition() const;
virtual int32_t getDisplayId() const;
virtual void fade(Transition transition);
virtual void unfade(Transition transition);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 96d83a5..f6f5d3b 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -81,7 +81,7 @@
virtual PointerIconStyle getDefaultPointerIconId() = 0;
virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index c820d00..2378d42 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -60,7 +60,7 @@
virtual PointerIconStyle getDefaultPointerIconId() override;
virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
@@ -143,8 +143,8 @@
}
void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
- float /*xPos*/,
- float /*yPos*/) {
+ const FloatPoint& /*position*/
+) {
latestPointerDisplayId = displayId;
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 23f87ab..f86b9af 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1566,7 +1566,7 @@
FileInputStream in = null;
try {
in = new FileInputStream(fileDescriptor);
- loadAttributes(in);
+ loadAttributes(in, fileDescriptor);
} finally {
closeQuietly(in);
if (isFdDuped) {
@@ -1637,7 +1637,7 @@
mSeekableFileDescriptor = null;
}
}
- loadAttributes(inputStream);
+ loadAttributes(inputStream, null);
}
/**
@@ -1963,7 +1963,7 @@
* This function decides which parser to read the image data according to the given input stream
* type and the content of the input stream.
*/
- private void loadAttributes(@NonNull InputStream in) {
+ private void loadAttributes(@NonNull InputStream in, @Nullable FileDescriptor fd) {
if (in == null) {
throw new NullPointerException("inputstream shouldn't be null");
}
@@ -1993,7 +1993,7 @@
break;
}
case IMAGE_TYPE_HEIF: {
- getHeifAttributes(inputStream);
+ getHeifAttributes(inputStream, fd);
break;
}
case IMAGE_TYPE_ORF: {
@@ -2580,7 +2580,7 @@
} else if (isSeekableFD(in.getFD())) {
mSeekableFileDescriptor = in.getFD();
}
- loadAttributes(in);
+ loadAttributes(in, null);
} finally {
closeQuietly(in);
if (modernFd != null) {
@@ -3068,59 +3068,66 @@
}
}
- private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
+ private void getHeifAttributes(ByteOrderedDataInputStream in, @Nullable FileDescriptor fd)
+ throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
- retriever.setDataSource(new MediaDataSource() {
- long mPosition;
+ if (fd != null) {
+ retriever.setDataSource(fd);
+ } else {
+ retriever.setDataSource(new MediaDataSource() {
+ long mPosition;
- @Override
- public void close() throws IOException {}
+ @Override
+ public void close() throws IOException {}
- @Override
- public int readAt(long position, byte[] buffer, int offset, int size)
- throws IOException {
- if (size == 0) {
- return 0;
- }
- if (position < 0) {
+ @Override
+ public int readAt(long position, byte[] buffer, int offset, int size)
+ throws IOException {
+ if (size == 0) {
+ return 0;
+ }
+ if (position < 0) {
+ return -1;
+ }
+ try {
+ if (mPosition != position) {
+ // We don't allow seek to positions after the available bytes,
+ // the input stream won't be able to seek back then.
+ // However, if we hit an exception before (mPosition set to -1),
+ // let it try the seek in hope it might recover.
+ if (mPosition >= 0 && position >= mPosition + in.available()) {
+ return -1;
+ }
+ in.seek(position);
+ mPosition = position;
+ }
+
+ // If the read will cause us to go over the available bytes,
+ // reduce the size so that we stay in the available range.
+ // Otherwise the input stream may not be able to seek back.
+ if (size > in.available()) {
+ size = in.available();
+ }
+
+ int bytesRead = in.read(buffer, offset, size);
+ if (bytesRead >= 0) {
+ mPosition += bytesRead;
+ return bytesRead;
+ }
+ } catch (IOException e) {
+ // absorb the exception and fall through to the 'failed read' path below
+ }
+ mPosition = -1; // need to seek on next read
return -1;
}
- try {
- if (mPosition != position) {
- // We don't allow seek to positions after the available bytes,
- // the input stream won't be able to seek back then.
- // However, if we hit an exception before (mPosition set to -1),
- // let it try the seek in hope it might recover.
- if (mPosition >= 0 && position >= mPosition + in.available()) {
- return -1;
- }
- in.seek(position);
- mPosition = position;
- }
- // If the read will cause us to go over the available bytes,
- // reduce the size so that we stay in the available range.
- // Otherwise the input stream may not be able to seek back.
- if (size > in.available()) {
- size = in.available();
- }
-
- int bytesRead = in.read(buffer, offset, size);
- if (bytesRead >= 0) {
- mPosition += bytesRead;
- return bytesRead;
- }
- } catch (IOException e) {}
- mPosition = -1; // need to seek on next read
- return -1;
- }
-
- @Override
- public long getSize() throws IOException {
- return -1;
- }
- });
+ @Override
+ public long getSize() throws IOException {
+ return -1;
+ }
+ });
+ }
String exifOffsetStr = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 5bc8c04..0a0a626 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -436,7 +436,7 @@
if (mEventHandler != null) {
mEventHandler.sendMessage(
mEventHandler.obtainMessage(
- EventHandler.MSG_CAS_EVENT, event, arg, data));
+ EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data)));
}
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 9c629bb..b1b7d40 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1210,7 +1210,7 @@
/**
* A key describing the desired bitrate mode to be used by an encoder.
- * Constants are declared in {@link MediaCodecInfo.CodecCapabilities}.
+ * Constants are declared in {@link MediaCodecInfo.EncoderCapabilities}.
*
* @see MediaCodecInfo.EncoderCapabilities#isBitrateModeSupported(int)
*/
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 0e9ef4c..ae8121a 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -94,12 +94,17 @@
originatorIdentity.packageName = ActivityThread.currentOpPackageName();
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- List<ModuleProperties> modulePropertiesList = soundTriggerService
- .listModuleProperties(originatorIdentity);
- if (!modulePropertiesList.isEmpty()) {
+ ModuleProperties moduleProperties = soundTriggerService
+ .listModuleProperties(originatorIdentity)
+ .stream()
+ .filter(prop -> !prop.getSupportedModelArch()
+ .equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
+ if (moduleProperties != null) {
mSoundTriggerSession = soundTriggerService.attachAsOriginator(
originatorIdentity,
- modulePropertiesList.get(0),
+ moduleProperties,
mBinderToken);
} else {
mSoundTriggerSession = null;
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 7946baee..f939f52 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -280,7 +280,7 @@
}
case DO_NOTIFY_TV_MESSAGE: {
SomeArgs args = (SomeArgs) msg.obj;
- mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+ mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
break;
}
default: {
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 7d6a8b4..ef2b5a5 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -149,10 +149,45 @@
/**
* This constant is used as a {@link Bundle} key for TV messages. The value of the key
* identifies the stream on the TV input source for which the watermark event is relevant to.
+ *
+ * <p> Type: String
*/
public static final String TV_MESSAGE_KEY_STREAM_ID =
"android.media.tv.TvInputManager.stream_id";
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+ * identifies the subtype of the data, such as the format of the CC data. The format
+ * found at this key can then be used to identify how to parse the data at
+ * {@link #TV_MESSAGE_KEY_RAW_DATA}.
+ *
+ * To parse the raw data bsed on the subtype, please refer to the official documentation of the
+ * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the
+ * document for A/335 from the ATSC standard details how this data is formatted.
+ *
+ * Some other examples of common formats include:
+ * <ul>
+ * <li>Watermarking - ATSC A/336</li>
+ * <li>Closed Captioning - CTA 608-E</li>
+ * </ul>
+ *
+ * <p> Type: String
+ */
+ public static final String TV_MESSAGE_KEY_SUBTYPE =
+ "android.media.tv.TvInputManager.subtype";
+
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+ * stores the raw data contained in this TV Message. The format of this data is determined
+ * by the format defined by the subtype, found using the key at
+ * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
+ * information on how to parse this data.
+ *
+ * <p> Type: byte[]
+ */
+ public static final String TV_MESSAGE_KEY_RAW_DATA =
+ "android.media.tv.TvInputManager.raw_data";
+
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
static final int VIDEO_UNAVAILABLE_REASON_END = 18;
@@ -802,7 +837,13 @@
*
* @param session A {@link TvInputManager.Session} associated with this callback.
* @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
- * @param data The raw data of the message
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
+ *
*/
public void onTvMessage(Session session, @TvInputManager.TvMessageType int type,
Bundle data) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3c6ed91..0d283fa 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1030,7 +1030,12 @@
*
* @param type The of message that was sent, such as
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
- * @param data The data sent with the message.
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
*/
public void notifyTvMessage(@TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
@@ -1500,9 +1505,14 @@
*
* @param type The type of message received, such as
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
- * @param data The raw data of the message
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
*/
- public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
+ public void onTvMessage(@TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
}
@@ -2055,7 +2065,7 @@
onAdBufferReady(buffer);
}
- void onTvMessageReceived(String type, Bundle data) {
+ void onTvMessageReceived(int type, Bundle data) {
onTvMessage(type, data);
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 19a2e5d..8a886832 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -1254,7 +1254,12 @@
* @param inputId The ID of the TV input bound to this view.
* @param type The type of message received, such as
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
- * @param data The raw data of the message
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
*/
public void onTvMessage(@NonNull String inputId,
@TvInputManager.TvMessageType int type, @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7dfe16a..69ff9c6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -919,7 +919,12 @@
*
* @param type The type of message received, such as
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
- * @param data The raw data of the message
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
*/
public void onTvMessage(@TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1a0319b..80a1435 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -944,7 +944,12 @@
*
* @param type The type of message received, such as
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
- * @param data The raw data of the message
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
*/
public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
@NonNull Bundle data) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 7f4c03b..6f67d68 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -617,8 +617,6 @@
void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/SectionEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
const DemuxFilterSectionEvent §ionEvent = event.get<DemuxFilterEvent::Tag::section>();
jint tableId = sectionEvent.tableId;
@@ -626,23 +624,15 @@
jint sectionNum = sectionEvent.sectionNum;
jlong dataLength = sectionEvent.dataLength;
- jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
+ jobject obj = env->NewObject(mSectionEventClass, mSectionEventInitID, tableId, version,
+ sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MediaEvent");
- jmethodID eventInit = env->GetMethodID(
- eventClazz,
- "<init>",
- "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
- "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
- "Ljava/util/List;)V");
- jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
jobject audioDescriptor = nullptr;
@@ -650,8 +640,6 @@
jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
switch (mediaEvent.extraMetaData.getTag()) {
case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
- jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
- jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
const AudioExtraMetaData &ad =
mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
@@ -662,9 +650,9 @@
jbyte adGainFront = ad.adGainFront;
jbyte adGainSurround = ad.adGainSurround;
- audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
- adGainCenter, adGainFront, adGainSurround);
- env->DeleteLocalRef(adClazz);
+ audioDescriptor = env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID, adFade,
+ adPan, versionTextTag, adGainCenter, adGainFront,
+ adGainSurround);
break;
}
case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
@@ -705,10 +693,10 @@
sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
}
- jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
- dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
- mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor,
- presentationsJObj);
+ jobject obj = env->NewObject(mMediaEventClass, mMediaEventInitID, streamId, isPtsPresent, pts,
+ isDtsPresent, dts, dataLength, offset, nullptr, isSecureMemory,
+ avDataId, mpuSequenceNumber, isPesPrivateData, sc,
+ audioDescriptor, presentationsJObj);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -717,7 +705,7 @@
new MediaEvent(mFilterClient, dupFromAidl(mediaEvent.avMemory),
mediaEvent.avDataId, dataLength + offset, obj);
mediaEventSp->mAvHandleRefCnt++;
- env->SetLongField(obj, eventContext, (jlong)mediaEventSp.get());
+ env->SetLongField(obj, mMediaEventFieldContextID, (jlong)mediaEventSp.get());
mediaEventSp->incStrong(obj);
}
@@ -726,32 +714,27 @@
env->DeleteLocalRef(audioDescriptor);
}
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
env->DeleteLocalRef(presentationsJObj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/PesEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(III)V");
const DemuxFilterPesEvent &pesEvent = event.get<DemuxFilterEvent::Tag::pes>();
jint streamId = pesEvent.streamId;
jint dataLength = pesEvent.dataLength;
jint mpuSequenceNumber = pesEvent.mpuSequenceNumber;
- jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
+ jobject obj = env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
+ mpuSequenceNumber);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJI)V");
const DemuxFilterTsRecordEvent &tsRecordEvent = event.get<DemuxFilterEvent::Tag::tsRecord>();
DemuxPid pid = tsRecordEvent.pid;
@@ -781,18 +764,15 @@
jlong pts = tsRecordEvent.pts;
jint firstMbInSlice = tsRecordEvent.firstMbInSlice;
- jobject obj =
- env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
+ jobject obj = env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
+ byteNumber, pts, firstMbInSlice);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJII)V");
const DemuxFilterMmtpRecordEvent &mmtpRecordEvent =
event.get<DemuxFilterEvent::Tag::mmtpRecord>();
@@ -803,18 +783,15 @@
jint firstMbInSlice = mmtpRecordEvent.firstMbInSlice;
jlong tsIndexMask = mmtpRecordEvent.tsIndexMask;
- jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
- mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
+ jobject obj = env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID, scHevcIndexMask,
+ byteNumber, mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/DownloadEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIIII)V");
const DemuxFilterDownloadEvent &downloadEvent = event.get<DemuxFilterEvent::Tag::download>();
jint itemId = downloadEvent.itemId;
@@ -824,32 +801,27 @@
jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
jint dataLength = downloadEvent.dataLength;
- jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
- itemFragmentIndex, lastItemFragmentIndex, dataLength);
+ jobject obj = env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId, downloadId,
+ mpuSequenceNumber, itemFragmentIndex, lastItemFragmentIndex,
+ dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpPayloadEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
const DemuxFilterIpPayloadEvent &ipPayloadEvent = event.get<DemuxFilterEvent::Tag::ipPayload>();
jint dataLength = ipPayloadEvent.dataLength;
- jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
+ jobject obj = env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID, dataLength);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TemiEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(JB[B)V");
const DemuxFilterTemiEvent &temiEvent = event.get<DemuxFilterEvent::Tag::temi>();
jlong pts = temiEvent.pts;
@@ -859,63 +831,53 @@
jbyteArray array = env->NewByteArray(descrData.size());
env->SetByteArrayRegion(array, 0, descrData.size(), reinterpret_cast<jbyte *>(&descrData[0]));
- jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
+ jobject obj = env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag, array);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(array);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
const DemuxFilterMonitorEvent &scramblingStatus =
event.get<DemuxFilterEvent::Tag::monitorEvent>()
.get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
- jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
+ jobject obj = env->NewObject(mScramblingStatusEventClass, mScramblingStatusEventInitID,
+ scramblingStatus);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
const DemuxFilterMonitorEvent &cid = event.get<DemuxFilterEvent::Tag::monitorEvent>()
.get<DemuxFilterMonitorEvent::Tag::cid>();
- jobject obj = env->NewObject(eventClazz, eventInit, cid);
+ jobject obj = env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/RestartEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
- jobject obj = env->NewObject(eventClazz, eventInit, startId);
+ jobject obj = env->NewObject(mRestartEventClass, mRestartEventInitID, startId);
env->SetObjectArrayElement(arr, size, obj);
env->DeleteLocalRef(obj);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
ALOGV("FilterClientCallbackImpl::onFilterEvent");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
jobjectArray array;
if (!events.empty()) {
- array = env->NewObjectArray(events.size(), eventClazz, nullptr);
+ array = env->NewObjectArray(events.size(), mEventClass, nullptr);
}
for (int i = 0, arraySize = 0; i < events.size(); i++) {
@@ -1004,7 +966,6 @@
"Filter object has been freed. Ignoring callback.");
}
env->DeleteLocalRef(array);
- env->DeleteLocalRef(eventClazz);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -1040,6 +1001,67 @@
mSharedFilter = true;
}
+FilterClientCallbackImpl::FilterClientCallbackImpl() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ ScopedLocalRef eventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/FilterEvent"));
+ ScopedLocalRef sectionEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/SectionEvent"));
+ ScopedLocalRef mediaEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/MediaEvent"));
+ ScopedLocalRef audioDescriptorClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/AudioDescriptor"));
+ ScopedLocalRef pesEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/PesEvent"));
+ ScopedLocalRef tsRecordEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/TsRecordEvent"));
+ ScopedLocalRef mmtpRecordEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent"));
+ ScopedLocalRef downloadEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/DownloadEvent"));
+ ScopedLocalRef ipPayloadEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/IpPayloadEvent"));
+ ScopedLocalRef temiEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/TemiEvent"));
+ ScopedLocalRef scramblingStatusEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent"));
+ ScopedLocalRef ipCidChangeEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent"));
+ ScopedLocalRef restartEventClass =
+ ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/RestartEvent"));
+ mEventClass = (jclass) env->NewGlobalRef(eventClass.get());
+ mSectionEventClass = (jclass) env->NewGlobalRef(sectionEventClass.get());
+ mMediaEventClass = (jclass) env->NewGlobalRef(mediaEventClass.get());
+ mAudioDescriptorClass = (jclass) env->NewGlobalRef(audioDescriptorClass.get());
+ mPesEventClass = (jclass) env->NewGlobalRef(pesEventClass.get());
+ mTsRecordEventClass = (jclass) env->NewGlobalRef(tsRecordEventClass.get());
+ mMmtpRecordEventClass = (jclass) env->NewGlobalRef(mmtpRecordEventClass.get());
+ mDownloadEventClass = (jclass) env->NewGlobalRef(downloadEventClass.get());
+ mIpPayloadEventClass = (jclass) env->NewGlobalRef(ipPayloadEventClass.get());
+ mTemiEventClass = (jclass) env->NewGlobalRef(temiEventClass.get());
+ mScramblingStatusEventClass = (jclass) env->NewGlobalRef(scramblingStatusEventClass.get());
+ mIpCidChangeEventClass = (jclass) env->NewGlobalRef(ipCidChangeEventClass.get());
+ mRestartEventClass = (jclass) env->NewGlobalRef(restartEventClass.get());
+ mSectionEventInitID = env->GetMethodID(mSectionEventClass, "<init>", "(IIIJ)V");
+ mMediaEventInitID = env->GetMethodID(
+ mMediaEventClass,
+ "<init>",
+ "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
+ "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
+ "Ljava/util/List;)V");
+ mAudioDescriptorInitID = env->GetMethodID(mAudioDescriptorClass, "<init>", "(BBCBBB)V");
+ mPesEventInitID = env->GetMethodID(mPesEventClass, "<init>", "(III)V");
+ mTsRecordEventInitID = env->GetMethodID(mTsRecordEventClass, "<init>", "(IIIJJI)V");
+ mMmtpRecordEventInitID = env->GetMethodID(mMmtpRecordEventClass, "<init>", "(IJIJII)V");
+ mDownloadEventInitID = env->GetMethodID(mDownloadEventClass, "<init>", "(IIIIII)V");
+ mIpPayloadEventInitID = env->GetMethodID(mIpPayloadEventClass, "<init>", "(I)V");
+ mTemiEventInitID = env->GetMethodID(mTemiEventClass, "<init>", "(JB[B)V");
+ mScramblingStatusEventInitID = env->GetMethodID(mScramblingStatusEventClass, "<init>", "(I)V");
+ mIpCidChangeEventInitID = env->GetMethodID(mIpCidChangeEventClass, "<init>", "(I)V");
+ mRestartEventInitID = env->GetMethodID(mRestartEventClass, "<init>", "(I)V");
+ mMediaEventFieldContextID = env->GetFieldID(mMediaEventClass, "mNativeContext", "J");
+}
+
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (mFilterObj != nullptr) {
@@ -1047,6 +1069,19 @@
mFilterObj = nullptr;
}
mFilterClient = nullptr;
+ env->DeleteGlobalRef(mEventClass);
+ env->DeleteGlobalRef(mSectionEventClass);
+ env->DeleteGlobalRef(mMediaEventClass);
+ env->DeleteGlobalRef(mAudioDescriptorClass);
+ env->DeleteGlobalRef(mPesEventClass);
+ env->DeleteGlobalRef(mTsRecordEventClass);
+ env->DeleteGlobalRef(mMmtpRecordEventClass);
+ env->DeleteGlobalRef(mDownloadEventClass);
+ env->DeleteGlobalRef(mIpPayloadEventClass);
+ env->DeleteGlobalRef(mTemiEventClass);
+ env->DeleteGlobalRef(mScramblingStatusEventClass);
+ env->DeleteGlobalRef(mIpCidChangeEventClass);
+ env->DeleteGlobalRef(mRestartEventClass);
}
/////////////// FrontendClientCallbackImpl ///////////////////////
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 2bb14f6..6b1b6b1 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -125,6 +125,7 @@
};
struct FilterClientCallbackImpl : public FilterClientCallback {
+ FilterClientCallbackImpl();
~FilterClientCallbackImpl();
virtual void onFilterEvent(const vector<DemuxFilterEvent>& events);
virtual void onFilterStatus(const DemuxFilterStatus status);
@@ -135,6 +136,32 @@
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ jclass mEventClass;
+ jclass mSectionEventClass;
+ jclass mMediaEventClass;
+ jclass mAudioDescriptorClass;
+ jclass mPesEventClass;
+ jclass mTsRecordEventClass;
+ jclass mMmtpRecordEventClass;
+ jclass mDownloadEventClass;
+ jclass mIpPayloadEventClass;
+ jclass mTemiEventClass;
+ jclass mScramblingStatusEventClass;
+ jclass mIpCidChangeEventClass;
+ jclass mRestartEventClass;
+ jmethodID mSectionEventInitID;
+ jmethodID mMediaEventInitID;
+ jmethodID mAudioDescriptorInitID;
+ jmethodID mPesEventInitID;
+ jmethodID mTsRecordEventInitID;
+ jmethodID mMmtpRecordEventInitID;
+ jmethodID mDownloadEventInitID;
+ jmethodID mIpPayloadEventInitID;
+ jmethodID mTemiEventInitID;
+ jmethodID mScramblingStatusEventInitID;
+ jmethodID mIpCidChangeEventInitID;
+ jmethodID mRestartEventInitID;
+ jfieldID mMediaEventFieldContextID;
bool mSharedFilter;
void getSectionEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
void getMediaEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index e91d35b..f4e89a14 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="app_name">CarrierDefaultApp</string>
+ <string name="app_name">Carrier Communications</string>
<string name="android_system_label">Mobile Carrier</string>
<string name="portal_notification_id">Mobile data has run out</string>
<string name="no_data_notification_id">Your mobile data has been deactivated</string>
@@ -17,9 +17,9 @@
<!-- Telephony notification channel name for performance boost notifications. -->
<string name="performance_boost_notification_channel">Performance boost</string>
<!-- Notification title text for the performance boost notification. -->
- <string name="performance_boost_notification_title">Improve your app experience</string>
+ <string name="performance_boost_notification_title">5G options from your carrier</string>
<!-- Notification detail text for the performance boost notification. -->
- <string name="performance_boost_notification_detail">Tap to visit %s\'s website and learn more</string>
+ <string name="performance_boost_notification_detail">Visit %s\'s website to see options for your app experience</string>
<!-- Notification button text to cancel the performance boost notification. -->
<string name="performance_boost_notification_button_not_now">Not now</string>
<!-- Notification button text to manage the performance boost notification. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index dd60763..5632458 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -250,7 +250,7 @@
leftButton = if (totalEntriesCount > 1) {
{
ActionButton(
- stringResource(R.string.get_dialog_use_saved_passkey_for),
+ stringResource(R.string.get_dialog_title_sign_in_options),
onMoreOptionSelected
)
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
index 1614188..9c2064c 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
@@ -22,89 +22,89 @@
key GRAVE {
label: '`'
- base, capslock: '\u0630'
+ base: '\u0630'
shift: '\u0651'
}
key 1 {
label: '1'
base: '\u0661'
- shift: '!'
capslock: '1'
+ shift: '!'
}
key 2 {
label: '2'
base: '\u0662'
- shift: '@'
capslock: '2'
+ shift: '@'
}
key 3 {
label: '3'
base: '\u0663'
- shift: '#'
capslock: '3'
+ shift: '#'
}
key 4 {
label: '4'
base: '\u0664'
- shift: '$'
capslock: '4'
+ shift: '$'
}
key 5 {
label: '5'
base: '\u0665'
- shift: '%'
capslock: '5'
+ shift: '%'
}
key 6 {
label: '6'
base: '\u0666'
- shift: '^'
capslock: '6'
+ shift: '^'
}
key 7 {
label: '7'
base: '\u0667'
- shift: '&'
capslock: '7'
+ shift: '&'
}
key 8 {
label: '8'
base: '\u0668'
- shift: '*'
capslock: '8'
+ shift: '*'
}
key 9 {
label: '9'
base: '\u0669'
- shift: ')'
capslock: '9'
+ shift: ')'
}
key 0 {
label: '0'
base: '\u0660'
- shift: '('
capslock: '0'
+ shift: '('
}
key MINUS {
label: '-'
- base, capslock: '-'
+ base: '-'
shift: '_'
}
key EQUALS {
label: '='
- base, capslock: '='
+ base: '='
shift: '+'
}
@@ -112,79 +112,79 @@
key Q {
label: 'Q'
- base, capslock: '\u0636'
+ base: '\u0636'
shift: '\u064e'
}
key W {
label: 'W'
- base, capslock: '\u0635'
+ base: '\u0635'
shift: '\u064b'
}
key E {
label: 'E'
- base, capslock: '\u062b'
+ base: '\u062b'
shift: '\u064f'
}
key R {
label: 'R'
- base, capslock: '\u0642'
+ base: '\u0642'
shift: '\u064c'
}
key T {
label: 'T'
- base, capslock: '\u0641'
+ base: '\u0641'
shift: '\ufef9'
}
key Y {
label: 'Y'
- base, capslock: '\u063a'
+ base: '\u063a'
shift: '\u0625'
}
key U {
label: 'U'
- base, capslock: '\u0639'
+ base: '\u0639'
shift: '\u2018'
}
key I {
label: 'I'
- base, capslock: '\u0647'
+ base: '\u0647'
shift: '\u00f7'
}
key O {
label: 'O'
- base, capslock: '\u062e'
+ base: '\u062e'
shift: '\u00d7'
}
key P {
label: 'P'
- base, capslock: '\u062d'
+ base: '\u062d'
shift: '\u061b'
}
key LEFT_BRACKET {
label: ']'
- base, capslock: '\u062c'
+ base: '\u062c'
shift: '>'
}
key RIGHT_BRACKET {
label: '['
- base, capslock: '\u062f'
+ base: '\u062f'
shift: '<'
}
key BACKSLASH {
label: '\\'
- base, capslock: '\\'
+ base: '\\'
shift: '|'
}
@@ -192,67 +192,67 @@
key A {
label: 'A'
- base, capslock: '\u0634'
+ base: '\u0634'
shift: '\u0650'
}
key S {
label: 'S'
- base, capslock: '\u0633'
+ base: '\u0633'
shift: '\u064d'
}
key D {
label: 'D'
- base, capslock: '\u064a'
+ base: '\u064a'
shift: ']'
}
key F {
label: 'F'
- base, capslock: '\u0628'
+ base: '\u0628'
shift: '['
}
key G {
label: 'G'
- base, capslock: '\u0644'
+ base: '\u0644'
shift: '\ufef7'
}
key H {
label: 'H'
- base, capslock: '\u0627'
+ base: '\u0627'
shift: '\u0623'
}
key J {
label: 'J'
- base, capslock: '\u062a'
+ base: '\u062a'
shift: '\u0640'
}
key K {
label: 'K'
- base, capslock: '\u0646'
+ base: '\u0646'
shift: '\u060c'
}
key L {
label: 'L'
- base, capslock: '\u0645'
+ base: '\u0645'
shift: '/'
}
key SEMICOLON {
label: ';'
- base, capslock: '\u0643'
+ base: '\u0643'
shift: ':'
}
key APOSTROPHE {
label: '\''
- base, capslock: '\u0637'
+ base: '\u0637'
shift: '"'
}
@@ -260,60 +260,60 @@
key Z {
label: 'Z'
- base, capslock: '\u0626'
+ base: '\u0626'
shift: '~'
}
key X {
label: 'X'
- base, capslock: '\u0621'
+ base: '\u0621'
shift: '\u0652'
}
key C {
label: 'C'
- base, capslock: '\u0624'
+ base: '\u0624'
shift: '}'
}
key V {
label: 'V'
- base, capslock: '\u0631'
+ base: '\u0631'
shift: '{'
}
key B {
label: 'B'
- base, capslock: '\ufefb'
+ base: '\ufefb'
shift: '\ufef5'
}
key N {
label: 'N'
- base, capslock: '\u0649'
+ base: '\u0649'
shift: '\u0622'
}
key M {
label: 'M'
- base, capslock: '\u0629'
+ base: '\u0629'
shift: '\u2019'
}
key COMMA {
label: ','
- base, capslock: '\u0648'
+ base: '\u0648'
shift: ','
}
key PERIOD {
label: '.'
- base, capslock: '\u0632'
+ base: '\u0632'
shift: '.'
}
key SLASH {
label: '/'
- base, capslock: '\u0638'
+ base: '\u0638'
shift: '\u061f'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
index 69490cc..3f5e894 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
@@ -107,72 +107,84 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: '\u00dc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: '\u0130'
base: 'i'
shift, capslock: '\u0130'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00d6'
base: '\u00f6'
shift: '\u00d6'
+ shift+capslock: '\u00f6'
}
key RIGHT_BRACKET {
label: '\u011e'
base: '\u011f'
shift: '\u011e'
+ shift+capslock: '\u011f'
}
key BACKSLASH {
@@ -187,66 +199,77 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: 'I'
base: '\u0131'
shift: 'I'
+ shift+capslock: '\u0131'
}
key APOSTROPHE {
label: '\u018f'
base: '\u0259'
shift: '\u018f'
+ shift+capslock: '\u0259'
}
### ROW 4
@@ -255,54 +278,63 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
label: '\u00c7'
base: '\u00e7'
shift: '\u00c7'
+ shift+capslock: '\u00e7'
}
key PERIOD {
label: '\u015e'
base: '\u015f'
shift: '\u015e'
+ shift+capslock: '\u015f'
}
key SLASH {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
index 3deb9dd..6751e1d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
@@ -24,6 +24,7 @@
label: '\u0401'
base: '\u0451'
shift, capslock: '\u0401'
+ shift+capslock: '\u0451'
ralt: '`'
ralt+shift: '~'
}
@@ -106,163 +107,203 @@
label: '\u0419'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0426'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0423'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u041a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0415'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u041d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0413'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0428'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u040E'
base: '\u045E'
shift, capslock: '\u040E'
+ shift+capslock: '\u045E'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0417'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u0425'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: '['
- ralt+shift: '{'
+ shift+ralt: '{'
}
key RIGHT_BRACKET {
label: '\u0027'
base: '\u0027'
- shift, capslock: '\u0027'
ralt: ']'
- ralt+shift: '}'
+ shift+ralt: '}'
}
### ROW 3
key A {
label: '\u0424'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u042b'
base: '\u044b'
shift, capslock: '\u042b'
+ shift+capslock: '\u044b'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0412'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u0410'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u041f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0420'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u041e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u041b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u0414'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u0416'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: ';'
- ralt+shift: ':'
+ shift+ralt: ':'
}
key APOSTROPHE {
label: '\u042d'
base: '\u044d'
shift, capslock: '\u042d'
+ shift+capslock: '\u044d'
ralt: '\''
- ralt+shift: '"'
+ shift+ralt: '"'
}
key BACKSLASH {
label: '\\'
@@ -275,69 +316,85 @@
label: '\u042f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0427'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0421'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u041c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0406'
base: '\u0456'
shift, capslock: '\u0406'
+ shift+capslock: '\u0456'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0422'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u042c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u0411'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: ','
- ralt+shift: '<'
+ shift+ralt: '<'
}
key PERIOD {
label: '\u042e'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: '.'
- ralt+shift: '>'
+ shift+ralt: '>'
}
key SLASH {
label: '.'
base: '.'
shift: ','
ralt: '/'
- ralt+shift: '?'
+ shift+ralt: '?'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
index f2c39ce..d529311 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
@@ -122,18 +122,21 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -141,42 +144,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -199,60 +209,70 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key APOSTROPHE {
@@ -282,36 +302,42 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
index 140c7ac..ad3199f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
@@ -115,6 +115,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '/'
}
@@ -122,6 +123,7 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '?'
}
@@ -129,6 +131,7 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -136,42 +139,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -193,60 +203,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00c7'
base: '\u00e7'
shift, capslock: '\u00c7'
+ shift+capslock: '\u00e7'
}
key APOSTROPHE {
@@ -258,7 +278,7 @@
key BACKSLASH {
label: ']'
base: ']'
- shift, capslock: '}'
+ shift: '}'
ralt: '\u00ba'
}
@@ -274,18 +294,21 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u20a2'
}
@@ -293,24 +316,28 @@
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'n'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
index c56367e..94ffbd0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
@@ -27,7 +27,7 @@
key GRAVE {
label: '`'
base: '`'
- shift, capslock: '~'
+ shift: '~'
ralt: '`'
ralt+shift: '~'
}
@@ -123,89 +123,109 @@
label: ','
base: ','
shift: '\u044b'
- capslock: '\u042b'
+ shift+capslock: '\u042b'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0423'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0415'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u0418'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0428'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u0429'
base: '\u0449'
shift, capslock: '\u0429'
+ shift+capslock: '\u0449'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u041a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0421'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u0414'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0417'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u0426'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: '['
- ralt+shift: '{'
+ shift+ralt: '{'
}
key RIGHT_BRACKET {
@@ -213,7 +233,7 @@
base: ';'
shift: '\u00a7'
ralt: ']'
- ralt+shift: '}'
+ shift+ralt: '}'
}
### ROW 3
@@ -222,78 +242,97 @@
label: '\u042c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u042f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0410'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u041e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u0416'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0413'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u0422'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u041d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u0412'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u041c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: ';'
ralt+shift: ':'
}
@@ -302,6 +341,7 @@
label: '\u0427'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: '\''
ralt+shift: '"'
}
@@ -328,62 +368,77 @@
label: '\u042e'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0419'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u042a'
base: '\u044a'
shift, capslock: '\u042a'
+ shift+capslock: '\u044a'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u042d'
base: '\u044d'
shift, capslock: '\u042d'
+ shift+capslock: '\u044d'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0424'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0425'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u041f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u0420'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: ','
ralt+shift: '<'
}
@@ -392,6 +447,7 @@
label: '\u041b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: '.'
ralt+shift: '>'
}
@@ -400,6 +456,7 @@
label: '\u0411'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: '/'
ralt+shift: '?'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
index 8878807..6314158 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
@@ -28,6 +28,7 @@
label: '`'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: '`'
ralt+shift: '~'
}
@@ -122,88 +123,108 @@
key Q {
label: '\u0447'
base: '\u0447'
- shift: '\u0427'
- capslock: '\u0427'
+ shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0448'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0435'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u0440'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0442'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u044a'
base: '\u044a'
shift, capslock: '\u042a'
+ shift+capslock: '\u044a'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0443'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0438'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u043e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u043f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u044f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: '['
ralt+shift: '{'
}
@@ -211,7 +232,8 @@
key RIGHT_BRACKET {
label: '\u0449'
base: '\u0449'
- shift: '\u0429'
+ shift, capslock: '\u0429'
+ shift+capslock: '\u0449'
ralt: ']'
ralt+shift: '}'
}
@@ -219,9 +241,8 @@
key BACKSLASH {
label: '\u044c'
base: '\u044c'
- shift: '\u042c'
- capslock: '\u042c'
- shift+capslock: '\u040d'
+ shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: '\\'
ralt+shift: '|'
}
@@ -232,78 +253,96 @@
label: '\u0430'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u0441'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0434'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u0444'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u0433'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0445'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u0439'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u043a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u043b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: ';'
base: ';'
- shift, capslock: ':'
+ shift: ':'
ralt: ';'
ralt+shift: ':'
}
@@ -311,7 +350,7 @@
key APOSTROPHE {
label: '\''
base: '\''
- shift, capslock: '"'
+ shift: '"'
ralt: '\''
ralt+shift: '"'
}
@@ -322,6 +361,7 @@
label: '\u045d'
base: '\u045d'
shift, capslock: '\u040d'
+ shift+capslock: '\u045d'
ralt: '\\'
ralt+shift: '|'
}
@@ -330,62 +370,76 @@
label: '\u0437'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0436'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0446'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u0432'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0431'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u043d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u043c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: ','
base: ','
- shift, capslock: '\u201e'
+ shift: '\u201e'
ralt: ','
ralt+shift: '<'
}
@@ -393,7 +447,7 @@
key PERIOD {
label: '.'
base: '.'
- shift, capslock: '\u201c'
+ shift: '\u201c'
ralt: '.'
ralt+shift: '>'
}
@@ -401,7 +455,7 @@
key SLASH {
label: '/'
base: '/'
- shift, capslock: '?'
+ shift: '?'
ralt: '/'
ralt+shift: '?'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
index 96445a4..1c774cc 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
@@ -122,6 +122,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\\'
}
@@ -129,6 +130,7 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '|'
}
@@ -136,6 +138,7 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -143,48 +146,56 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u0160'
base: '\u0161'
shift, capslock: '\u0160'
+ shift+capslock: '\u0161'
ralt: '\u00f7'
}
@@ -192,6 +203,7 @@
label: '\u0110'
base: '\u0111'
shift, capslock: '\u0110'
+ shift+capslock: '\u0111'
ralt: '\u00d7'
}
@@ -201,24 +213,28 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '['
}
@@ -226,6 +242,7 @@
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: ']'
}
@@ -233,40 +250,48 @@
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u0268'
- ralt+shift, ralt+capslock: '\u0197'
+ shift+ralt, capslock+ralt: '\u0197'
+ shift+capslock+ralt: '\u0268'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u0142'
- ralt+shift, ralt+capslock: '\u0141'
+ shift+ralt, capslock+ralt: '\u0141'
+ shift+capslock+ralt: '\u0142'
}
key SEMICOLON {
label: '\u010c'
base: '\u010d'
shift, capslock: '\u010c'
+ shift+capslock: '\u010d'
}
key APOSTROPHE {
label: '\u0106'
base: '\u0107'
shift, capslock: '\u0106'
+ shift+capslock: '\u0107'
ralt: '\u00df'
}
@@ -274,6 +299,7 @@
label: '\u017d'
base: '\u017e'
shift, capslock: '\u017d'
+ shift+capslock: '\u017e'
ralt: '\u00a4'
}
@@ -289,24 +315,28 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '@'
}
@@ -314,6 +344,7 @@
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '{'
}
@@ -321,6 +352,7 @@
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '}'
}
@@ -328,6 +360,7 @@
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00a7'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
index 32750e0..08b012e 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
@@ -131,18 +131,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -150,42 +153,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -211,54 +221,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -300,24 +319,28 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '@'
}
@@ -325,18 +348,21 @@
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
index 457d4da..cad262b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
@@ -131,18 +131,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -150,42 +153,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -211,54 +221,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -300,24 +319,28 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '@'
}
@@ -325,18 +348,21 @@
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
index 9168d12..83ee8c3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
@@ -115,76 +115,90 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\u00e2'
- ralt+capslock, shift+ralt: '\u00c2'
+ shift+ralt, capslock+ralt: '\u00c2'
+ shift+capslock+ralt: '\u00e2'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
- ralt+capslock: '\u20ac'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u0167'
- ralt+capslock, shift+ralt: '\u0166'
+ shift+ralt, capslock+ralt: '\u0166'
+ shift+capslock+ralt: '\u0167'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ef'
- ralt+capslock, shift+ralt: '\u00cf'
+ shift+ralt, capslock+ralt: '\u00cf'
+ shift+capslock+ralt: '\u00ef'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f5'
- ralt+capslock, shift+ralt: '\u00d5'
+ shift+ralt, capslock+ralt: '\u00d5'
+ shift+capslock+ralt: '\u00f5'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00c5'
base: '\u00e5'
shift, capslock: '\u00c5'
+ shift+capslock: '\u00e5'
}
key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- ralt+capslock, shift+ralt: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- ralt+capslock, shift+ralt: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0111'
- ralt+capslock, shift+ralt: '\u0110'
+ shift+ralt, capslock+ralt: '\u0110'
+ shift+capslock+ralt: '\u0111'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '\u01e5'
- ralt+capslock, shift+ralt: '\u01e4'
+ shift+ralt, capslock+ralt: '\u01e4'
+ shift+capslock+ralt: '\u01e5'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: '\u01e7'
- ralt+capslock, shift+ralt: '\u01e6'
+ shift+ralt, capslock+ralt: '\u01e6'
+ shift+capslock+ralt: '\u01e7'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u021f'
- ralt+capslock, shift+ralt: '\u021e'
+ shift+ralt, capslock+ralt: '\u021e'
+ shift+capslock+ralt: '\u021f'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u01e9'
- ralt+capslock, shift+ralt: '\u01e8'
+ shift+ralt, capslock+ralt: '\u01e8'
+ shift+capslock+ralt: '\u01e9'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00c6'
base: '\u00e6'
shift, capslock: '\u00c6'
+ shift+capslock: '\u00e6'
ralt: '\u00e4'
- ralt+capslock, shift+ralt: '\u00c4'
+ shift+ralt, capslock+ralt: '\u00c4'
+ shift+capslock+ralt: '\u00e4'
}
key APOSTROPHE {
label: '\u00d8'
base: '\u00f8'
shift, capslock: '\u00d8'
+ shift+capslock: '\u00f8'
ralt: '\u00f6'
- ralt+capslock, shift+ralt: '\u00d6'
+ shift+ralt, capslock+ralt: '\u00d6'
+ shift+capslock+ralt: '\u00f6'
}
key BACKSLASH {
@@ -299,53 +333,65 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- ralt+capslock, shift+ralt: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u010d'
- ralt+capslock, shift+ralt: '\u010c'
+ shift+ralt, capslock+ralt: '\u010c'
+ shift+capslock+ralt: '\u010d'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '\u01ef'
- ralt+capslock, shift+ralt: '\u01ee'
+ shift+ralt, capslock+ralt: '\u01ee'
+ shift+capslock+ralt: '\u01ef'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '\u0292'
- ralt+capslock, shift+ralt: '\u01b7'
+ shift+ralt, capslock+ralt: '\u01b7'
+ shift+capslock+ralt: '\u0292'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u014b'
- ralt+capslock, shift+ralt: '\u014a'
+ shift+ralt, capslock+ralt: '\u014a'
+ shift+capslock+ralt: '\u014b'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
- ralt, ralt+capslock: '\u00b5'
+ shift+capslock: 'm'
+ ralt: '\u00b5'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 6d9c2e5..93a5082 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -108,68 +108,82 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u00e9'
- shift+ralt: '\u00c9'
+ shift+ralt, capslock+ralt: '\u00c9'
+ shift+capslock+ralt: '\u00e9'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
ralt: '\u00fa'
- shift+ralt: '\u00da'
+ shift+ralt, capslock+ralt: '\u00da'
+ shift+capslock+ralt: '\u00fa'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ed'
- shift+ralt: '\u00cd'
+ shift+ralt, capslock+ralt: '\u00cd'
+ shift+capslock+ralt: '\u00ed'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f3'
- shift+ralt: '\u00d3'
+ shift+ralt, capslock+ralt: '\u00d3'
+ shift+capslock+ralt: '\u00f3'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -190,56 +204,66 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- shift+ralt: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -274,42 +298,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
index 050b149..da76448 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
@@ -106,60 +106,70 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -186,54 +196,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -254,42 +273,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
index 72e6d04..e52ccf0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
@@ -125,60 +125,70 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key SEMICOLON {
label: ';'
base: ';'
shift, capslock: ':'
+ shift+capslock: ':'
}
key LEFT_BRACKET {
@@ -205,54 +215,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
@@ -273,42 +292,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
index df6a3fd..6ff627b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
@@ -160,42 +160,49 @@
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SLASH {
@@ -222,60 +229,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key MINUS {
@@ -296,52 +313,61 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
index aa31493..dff17b3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
@@ -121,30 +121,37 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\u00e4'
shift+ralt, capslock+ralt: '\u00c4'
+ shift+capslock+ralt: '\u00e4'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '\u00e5'
shift+ralt, capslock+ralt: '\u00c5'
+ shift+capslock+ralt: '\u00e5'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u00e9'
shift+ralt, capslock+ralt: '\u00c9'
+ shift+capslock+ralt: '\u00e9'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
ralt: '\u00ae'
}
@@ -152,48 +159,60 @@
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u00fe'
shift+ralt, capslock+ralt: '\u00de'
+ shift+capslock+ralt: '\u00fe'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
ralt: '\u00fc'
shift+ralt, capslock+ralt: '\u00dc'
+ shift+capslock+ralt: '\u00fc'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
ralt: '\u00fa'
shift+ralt, capslock+ralt: '\u00da'
+ shift+capslock+ralt: '\u00fa'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ed'
shift+ralt, capslock+ralt: '\u00cd'
+ shift+capslock+ralt: '\u00ed'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f3'
shift+ralt, capslock+ralt: '\u00d3'
+ shift+capslock+ralt: '\u00f3'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
ralt: '\u00f6'
shift+ralt, capslock+ralt: '\u00d6'
+ shift+capslock+ralt: '\u00f6'
}
key LEFT_BRACKET {
@@ -224,14 +243,17 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- shift+ralt, ralt+capslock: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u00df'
shift+ralt: '\u00a7'
}
@@ -240,46 +262,55 @@
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u00f0'
shift+ralt, capslock+ralt: '\u00d0'
+ shift+capslock+ralt: '\u00f0'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u00f8'
shift+ralt, capslock+ralt: '\u00d8'
+ shift+capslock+ralt: '\u00f8'
}
key SEMICOLON {
@@ -312,20 +343,24 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u00e6'
shift+ralt, capslock+ralt: '\u00c6'
+ shift+capslock+ralt: '\u00e6'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u00a9'
shift+ralt: '\u00a2'
}
@@ -334,26 +369,31 @@
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u00f1'
shift+ralt, capslock+ralt: '\u00d1'
+ shift+capslock+ralt: '\u00f1'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
@@ -363,6 +403,7 @@
shift: '<'
ralt: '\u00e7'
shift+ralt, capslock+ralt: '\u00c7'
+ shift+capslock+ralt: '\u00e7'
}
key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
index fe82c8d..713afba 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -129,60 +129,70 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key SEMICOLON {
label: ';'
base: ';'
shift, capslock: ':'
+ shift+capslock: ':'
}
key LEFT_BRACKET {
@@ -209,48 +219,56 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key O {
@@ -263,6 +281,7 @@
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key APOSTROPHE {
@@ -277,42 +296,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
index ef545b8..27a03da 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
@@ -116,18 +116,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -135,54 +138,63 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00dc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
}
key RIGHT_BRACKET {
label: '\u00d5'
base: '\u00f5'
shift, capslock: '\u00d5'
+ shift+capslock: '\u00f5'
ralt: '\u00a7'
}
@@ -192,68 +204,80 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- ralt+shift, ralt+capslock: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
}
key APOSTROPHE {
label: '\u00c4'
base: '\u00e4'
shift, capslock: '\u00c4'
+ shift+capslock: '\u00e4'
ralt: '\u0302'
}
@@ -277,44 +301,52 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- ralt+shift, ralt+capslock: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
index b4deed4..79096ad 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
@@ -115,76 +115,90 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\u00e2'
- ralt+capslock, shift+ralt: '\u00c2'
+ shift+ralt, capslock+ralt: '\u00c2'
+ shift+capslock+ralt: '\u00e2'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
- ralt+capslock: '\u20ac'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u0167'
- ralt+capslock, shift+ralt: '\u0166'
+ shift+ralt, capslock+ralt: '\u0166'
+ shift+capslock+ralt: '\u0167'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ef'
- ralt+capslock, shift+ralt: '\u00cf'
+ shift+ralt, capslock+ralt: '\u00cf'
+ shift+capslock+ralt: '\u00ef'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f5'
- ralt+capslock, shift+ralt: '\u00d5'
+ shift+ralt, capslock+ralt: '\u00d5'
+ shift+capslock+ralt: '\u00f5'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00c5'
base: '\u00e5'
shift, capslock: '\u00c5'
+ shift+capslock: '\u00e5'
}
key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- ralt+capslock, shift+ralt: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- ralt+capslock, shift+ralt: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0111'
- ralt+capslock, shift+ralt: '\u0110'
+ shift+ralt, capslock+ralt: '\u0110'
+ shift+capslock+ralt: '\u0111'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '\u01e5'
- ralt+capslock, shift+ralt: '\u01e4'
+ shift+ralt, capslock+ralt: '\u01e4'
+ shift+capslock+ralt: '\u01e5'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: '\u01e7'
- ralt+capslock, shift+ralt: '\u01e6'
+ shift+ralt, capslock+ralt: '\u01e6'
+ shift+capslock+ralt: '\u01e7'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u021f'
- ralt+capslock, shift+ralt: '\u021e'
+ shift+ralt, capslock+ralt: '\u021e'
+ shift+capslock+ralt: '\u021f'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u01e9'
- ralt+capslock, shift+ralt: '\u01e8'
+ shift+ralt, capslock+ralt: '\u01e8'
+ shift+capslock+ralt: '\u01e9'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
ralt: '\u00f8'
- ralt+capslock, shift+ralt: '\u00d8'
+ shift+ralt, capslock+ralt: '\u00d8'
+ shift+capslock+ralt: '\u00f8'
}
key APOSTROPHE {
label: '\u00c4'
base: '\u00e4'
shift, capslock: '\u00c4'
+ shift+capslock: '\u00e4'
ralt: '\u00e6'
- ralt+capslock, shift+ralt: '\u00c6'
+ shift+ralt, capslock+ralt: '\u00c6'
+ shift+capslock+ralt: '\u00e6'
}
key BACKSLASH {
@@ -299,53 +333,65 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- ralt+capslock, shift+ralt: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u010d'
- ralt+capslock, shift+ralt: '\u010c'
+ shift+ralt, capslock+ralt: '\u010c'
+ shift+capslock+ralt: '\u010d'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '\u01ef'
- ralt+capslock, shift+ralt: '\u01ee'
+ shift+ralt, capslock+ralt: '\u01ee'
+ shift+capslock+ralt: '\u01ef'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '\u0292'
- ralt+capslock, shift+ralt: '\u01b7'
+ shift+ralt, capslock+ralt: '\u01b7'
+ shift+capslock+ralt: '\u0292'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u014b'
- ralt+capslock, shift+ralt: '\u014a'
+ shift+ralt, capslock+ralt: '\u014a'
+ shift+capslock+ralt: '\u014b'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
- ralt, ralt+capslock: '\u00b5'
+ shift+capslock: 'm'
+ ralt: '\u00b5'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 89e83da..4906304 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -123,18 +123,21 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -142,42 +145,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -199,60 +209,70 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key APOSTROPHE {
@@ -279,36 +299,42 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
index 55ddd60..03b5c19 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
@@ -119,54 +119,63 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00a7'
}
@@ -174,6 +183,7 @@
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
ralt: '\u00b6'
}
@@ -196,54 +206,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -279,42 +298,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
@@ -335,5 +361,6 @@
label: '\u00c9'
base: '\u00e9'
shift, capslock: '\u00c9'
+ shift+capslock: '\u00e9'
ralt: '\u0301'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
index 35b66a3..a8f229f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
@@ -28,6 +28,7 @@
label: '\u201e'
base: '\u201e'
shift, capslock: '\u201c'
+ shift+capslock: '\u201e'
ralt: '`'
ralt+shift: '~'
}
@@ -128,79 +129,92 @@
label: '\u10e5'
base: '\u10e5'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u10ec'
base: '\u10ec'
shift, capslock: '\u10ed'
+ shift+capslock: '\u10ec'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u10d4'
base: '\u10d4'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u10e0'
base: '\u10e0'
shift, capslock: '\u10e6'
+ shift+capslock: '\u10e0'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u10e2'
base: '\u10e2'
shift, capslock: '\u10d7'
+ shift+capslock: '\u10e2'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u10e7'
base: '\u10e7'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u10e3'
base: '\u10e3'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u10d8'
base: '\u10d8'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u10dd'
base: '\u10dd'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u10de'
base: '\u10de'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '['
base: '['
- shift, capslock: '{'
+ shift: '{'
ralt: '['
ralt+shift: '{'
}
@@ -208,7 +222,7 @@
key RIGHT_BRACKET {
label: ']'
base: ']'
- shift, capslock: '}'
+ shift: '}'
ralt: ']'
ralt+shift: '}'
}
@@ -227,72 +241,84 @@
label: '\u10d0'
base: '\u10d0'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u10e1'
base: '\u10e1'
shift, capslock: '\u10e8'
+ shift+capslock: '\u10e1'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u10d3'
base: '\u10d3'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u10e4'
base: '\u10e4'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u10d2'
base: '\u10d2'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u10f0'
base: '\u10f0'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u10ef'
base: '\u10ef'
shift, capslock: '\u10df'
+ shift+capslock: '\u10ef'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u10d9'
base: '\u10d9'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u10da'
base: '\u10da'
shift, capslock: '\u20be'
+ shift+capslock: '\u10da'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: ';'
base: ';'
- shift, capslock: ':'
+ shift: ':'
ralt: ';'
ralt+shift: ':'
}
@@ -300,7 +326,7 @@
key APOSTROPHE {
label: '\''
base: '\''
- shift, capslock: '"'
+ shift: '"'
ralt: '\''
ralt+shift: '"'
}
@@ -311,57 +337,66 @@
label: '\u10d6'
base: '\u10d6'
shift, capslock: '\u10eb'
+ shift+capslock: '\u10d6'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u10ee'
base: '\u10ee'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u10ea'
base: '\u10ea'
shift, capslock: '\u10e9'
+ shift+capslock: '\u10ea'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u10d5'
base: '\u10d5'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u10d1'
base: '\u10d1'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u10dc'
base: '\u10dc'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u10db'
base: '\u10db'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: ','
base: ','
- shift, capslock: '<'
+ shift: '<'
ralt: ','
ralt+shift: '<'
}
@@ -369,7 +404,7 @@
key PERIOD {
label: '.'
base: '.'
- shift, capslock: '>'
+ shift: '>'
ralt: '.'
ralt+shift: '>'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
index d9caa32..23ccc9a 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
@@ -18,7 +18,7 @@
type OVERLAY
-map key 12 SLASH # § ? \
+map key 12 SLASH # § ? \
map key 21 Z
map key 44 Y
map key 53 MINUS # - _
@@ -117,6 +117,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '@'
}
@@ -124,12 +125,14 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -137,48 +140,56 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00dc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
}
key RIGHT_BRACKET {
@@ -194,66 +205,77 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
}
key APOSTROPHE {
label: '\u00c4'
base: '\u00e4'
shift, capslock: '\u00c4'
+ shift+capslock: '\u00e4'
}
key BACKSLASH {
@@ -275,42 +297,49 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
index a7684e1..6eff114 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
@@ -24,88 +24,88 @@
key GRAVE {
label: '`'
- base, capslock: '`'
+ base: '`'
shift: '~'
}
key 1 {
label: '1'
- base, capslock: '1'
+ base: '1'
shift: '!'
}
key 2 {
label: '2'
- base, capslock: '2'
+ base: '2'
shift: '@'
ralt: '\u00b2'
}
key 3 {
label: '3'
- base, capslock: '3'
+ base: '3'
shift: '#'
ralt: '\u00b3'
}
key 4 {
label: '4'
- base, capslock: '4'
+ base: '4'
shift: '$'
ralt: '\u00a3'
}
key 5 {
label: '5'
- base, capslock: '5'
+ base: '5'
shift: '%'
ralt: '\u00a7'
}
key 6 {
label: '6'
- base, capslock: '6'
+ base: '6'
shift: '^'
ralt: '\u00b6'
}
key 7 {
label: '7'
- base, capslock: '7'
+ base: '7'
shift: '&'
}
key 8 {
label: '8'
- base, capslock: '8'
+ base: '8'
shift: '*'
ralt: '\u00a4'
}
key 9 {
label: '9'
- base, capslock: '9'
+ base: '9'
shift: '('
ralt: '\u00a6'
}
key 0 {
label: '0'
- base, capslock: '0'
+ base: '0'
shift: ')'
ralt: '\u00b0'
}
key MINUS {
label: '-'
- base, capslock: '-'
+ base: '-'
shift: '_'
ralt: '\u00b1'
}
key EQUALS {
label: '='
- base, capslock: '='
+ base: '='
shift: '+'
ralt: '\u00bd'
}
@@ -114,13 +114,13 @@
key Q {
label: 'Q'
- base, capslock: ';'
+ base: ';'
shift: ':'
}
key W {
label: 'W'
- base, capslock: '\u03c2'
+ base: '\u03c2'
shift: '\u0385'
}
@@ -128,6 +128,7 @@
label: 'E'
base: '\u03b5'
shift, capslock: '\u0395'
+ shift+capslock: '\u03b5'
ralt: '\u20ac'
}
@@ -135,6 +136,7 @@
label: 'R'
base: '\u03c1'
shift, capslock: '\u03a1'
+ shift+capslock: '\u03c1'
ralt: '\u00ae'
}
@@ -142,12 +144,14 @@
label: 'T'
base: '\u03c4'
shift, capslock: '\u03a4'
+ shift+capslock: '\u03c4'
}
key Y {
label: 'Y'
base: '\u03c5'
shift, capslock: '\u03a5'
+ shift+capslock: '\u03c5'
ralt: '\u00a5'
}
@@ -155,36 +159,40 @@
label: 'U'
base: '\u03b8'
shift, capslock: '\u0398'
+ shift+capslock: '\u03b8'
}
key I {
label: 'I'
base: '\u03b9'
shift, capslock: '\u0399'
+ shift+capslock: '\u03b9'
}
key O {
label: 'O'
base: '\u03bf'
shift, capslock: '\u039f'
+ shift+capslock: '\u03bf'
}
key P {
label: 'P'
base: '\u03c0'
shift, capslock: '\u03a0'
+ shift+capslock: '\u03c0'
}
key LEFT_BRACKET {
label: '['
- base, capslock: '['
+ base: '['
shift: '{'
ralt: '\u00ab'
}
key RIGHT_BRACKET {
label: ']'
- base, capslock: ']'
+ base: ']'
shift: '}'
ralt: '\u00bb'
}
@@ -195,59 +203,68 @@
label: 'A'
base: '\u03b1'
shift, capslock: '\u0391'
+ shift+capslock: '\u03b1'
}
key S {
label: 'S'
base: '\u03c3'
shift, capslock: '\u03a3'
+ shift+capslock: '\u03c3'
}
key D {
label: 'D'
base: '\u03b4'
shift, capslock: '\u0394'
+ shift+capslock: '\u03b4'
}
key F {
label: 'F'
base: '\u03c6'
shift, capslock: '\u03a6'
+ shift+capslock: '\u03c6'
}
key G {
label: 'G'
base: '\u03b3'
shift, capslock: '\u0393'
+ shift+capslock: '\u03b3'
}
key H {
label: 'H'
base: '\u03b7'
shift, capslock: '\u0397'
+ shift+capslock: '\u03b7'
}
key J {
label: 'J'
base: '\u03be'
shift, capslock: '\u039e'
+ shift+capslock: '\u03be'
}
key K {
label: 'K'
base: '\u03ba'
shift, capslock: '\u039a'
+ shift+capslock: '\u03ba'
}
key L {
label: 'L'
base: '\u03bb'
shift, capslock: '\u039b'
+ shift+capslock: '\u03bb'
}
key SEMICOLON {
label: ';'
- base, capslock: '\u0301'
+ base: '\u0301'
#should be \u0384 (greek tonos)
shift: '\u0308'
ralt: '\u0385'
@@ -255,13 +272,13 @@
key APOSTROPHE {
label: '\''
- base, capslock: '\''
+ base: '\''
shift: '"'
}
key BACKSLASH {
label: '\\'
- base, capslock: '\\'
+ base: '\\'
shift: '|'
ralt: '\u00ac'
}
@@ -270,7 +287,7 @@
key PLUS {
label: '<'
- base, capslock: '<'
+ base: '<'
shift: '>'
ralt: '\\'
shift+ralt: '|'
@@ -280,18 +297,21 @@
label: 'Z'
base: '\u03b6'
shift, capslock: '\u0396'
+ shift+capslock: '\u03b6'
}
key X {
label: 'X'
base: '\u03c7'
shift, capslock: '\u03a7'
+ shift+capslock: '\u03c7'
}
key C {
label: 'C'
base: '\u03c8'
shift, capslock: '\u03a8'
+ shift+capslock: '\u03c8'
ralt: '\u00a9'
}
@@ -299,40 +319,44 @@
label: 'V'
base: '\u03c9'
shift, capslock: '\u03a9'
+ shift+capslock: '\u03c9'
}
key B {
label: 'B'
base: '\u03b2'
shift, capslock: '\u0392'
+ shift+capslock: '\u03b2'
}
key N {
label: 'N'
base: '\u03bd'
shift, capslock: '\u039d'
+ shift+capslock: '\u03bd'
}
key M {
label: 'M'
base: '\u03bc'
shift, capslock: '\u039c'
+ shift+capslock: '\u03bc'
}
key COMMA {
label: ','
- base, capslock: ','
+ base: ','
shift: '<'
}
key PERIOD {
label: '.'
- base, capslock: '.'
+ base: '.'
shift: '>'
}
key SLASH {
label: '/'
- base, capslock: '/'
+ base: '/'
shift: '?'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
index 283cb4e..11ade42 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
@@ -121,18 +121,21 @@
label: 'Q'
base: '/'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: '\u0027'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: '\u05e7'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -140,24 +143,28 @@
label: 'R'
base: '\u05e8'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: '\u05d0'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: '\u05d8'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: '\u05d5'
shift, capslock: 'U'
+ shift+capslock: 'u'
ralt: '\u05f0'
}
@@ -165,29 +172,32 @@
label: 'I'
base: '\u05df'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: '\u05dd'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: '\u05e4'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: ']'
- base, capslock: ']'
+ base: ']'
shift: '}'
}
key RIGHT_BRACKET {
label: '['
- base, capslock: '['
+ base: '['
shift: '{'
}
@@ -197,36 +207,42 @@
label: 'A'
base: '\u05e9'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: '\u05d3'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: '\u05d2'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: '\u05db'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: '\u05e2'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: '\u05d9'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u05f2'
}
@@ -234,6 +250,7 @@
label: 'J'
base: '\u05d7'
shift, capslock: 'J'
+ shift+capslock: 'j'
ralt: '\u05f1'
}
@@ -241,12 +258,14 @@
label: 'K'
base: '\u05dc'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: '\u05da'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -254,6 +273,7 @@
base: '\u05e3'
shift: ':'
capslock: ';'
+ shift+capslock: ':'
}
key APOSTROPHE {
@@ -261,6 +281,7 @@
base: ','
shift: '"'
capslock: '\''
+ shift+capslock: '"'
}
key BACKSLASH {
@@ -273,7 +294,7 @@
key PLUS {
label: '\\'
- base, capslock: '\\'
+ base: '\\'
shift: '|'
}
@@ -281,42 +302,49 @@
label: 'Z'
base: '\u05d6'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: '\u05e1'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: '\u05d1'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: '\u05d4'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: '\u05e0'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: '\u05de'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: '\u05e6'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
@@ -324,6 +352,7 @@
base: '\u05ea'
shift: '>'
capslock: ','
+ shift+capslock: '>'
}
key PERIOD {
@@ -331,6 +360,7 @@
base: '\u05e5'
shift: '<'
capslock: '.'
+ shift+capslock: '<'
}
key SLASH {
@@ -338,4 +368,5 @@
base: '.'
shift: '?'
capslock: '/'
+ shift+capslock: '?'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
index dafb50b..6c947c7 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
@@ -101,6 +101,7 @@
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
ralt: '\u030b'
}
@@ -108,6 +109,7 @@
label: '\u00dc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
ralt: '\u0308'
}
@@ -115,6 +117,7 @@
label: '\u00d3'
base: '\u00f3'
shift, capslock: '\u00d3'
+ shift+capslock: '\u00f3'
ralt: '\u0327'
}
@@ -124,6 +127,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\\'
}
@@ -131,6 +135,7 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '|'
}
@@ -138,6 +143,7 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u00c4'
}
@@ -145,24 +151,28 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
ralt: '\u20ac'
}
@@ -170,6 +180,7 @@
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00cd'
}
@@ -177,18 +188,21 @@
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u0150'
base: '\u0151'
shift, capslock: '\u0150'
+ shift+capslock: '\u0151'
ralt: '\u00f7'
}
@@ -196,6 +210,7 @@
label: '\u00da'
base: '\u00fa'
shift, capslock: '\u00da'
+ shift+capslock: '\u00fa'
ralt: '\u00d7'
}
@@ -205,6 +220,7 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e4'
}
@@ -212,6 +228,7 @@
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0111'
}
@@ -219,6 +236,7 @@
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0110'
}
@@ -226,6 +244,7 @@
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '['
}
@@ -233,6 +252,7 @@
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: ']'
}
@@ -240,12 +260,14 @@
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
ralt: '\u00ed'
}
@@ -253,6 +275,7 @@
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u0197'
}
@@ -260,6 +283,7 @@
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u0141'
}
@@ -267,6 +291,7 @@
label: '\u00c9'
base: '\u00e9'
shift, capslock: '\u00c9'
+ shift+capslock: '\u00e9'
ralt: '$'
}
@@ -274,6 +299,7 @@
label: '\u00c1'
base: '\u00e1'
shift, capslock: '\u00c1'
+ shift+capslock: '\u00e1'
ralt: '\u00df'
}
@@ -281,6 +307,7 @@
label: '\u0170'
base: '\u0171'
shift, capslock: '\u0170'
+ shift+capslock: '\u0171'
ralt: '\u00a4'
}
@@ -290,6 +317,7 @@
label: '\u00cd'
base: '\u00ed'
shift, capslock: '\u00cd'
+ shift+capslock: '\u00ed'
ralt: '<'
}
@@ -297,6 +325,7 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
ralt: '>'
}
@@ -304,6 +333,7 @@
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
ralt: '#'
}
@@ -311,6 +341,7 @@
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '&'
}
@@ -318,6 +349,7 @@
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '@'
}
@@ -325,6 +357,7 @@
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '{'
}
@@ -332,6 +365,7 @@
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '}'
}
@@ -339,6 +373,7 @@
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
index 117f58b..5131b4f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
@@ -99,6 +99,7 @@
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
ralt: '\\'
}
@@ -114,6 +115,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '@'
}
@@ -121,12 +123,14 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -134,48 +138,56 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u0110'
base: '\u0111'
shift, capslock: '\u0110'
+ shift+capslock: '\u0111'
}
key RIGHT_BRACKET {
@@ -191,60 +203,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00c6'
base: '\u00e6'
shift, capslock: '\u00c6'
+ shift+capslock: '\u00e6'
}
key APOSTROPHE {
@@ -274,42 +296,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
ralt: '\u00b5'
}
@@ -329,4 +358,5 @@
label: '\u00de'
base: '\u00fe'
shift, capslock: '\u00de'
+ shift+capslock: '\u00fe'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
index bd2d25a..309d8b2 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
@@ -109,18 +109,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -128,42 +131,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -188,54 +198,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -270,42 +289,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
index d4bc0c0..3b77cb1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
@@ -119,70 +119,85 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u0113'
- shift+ralt, ralt+capslock: '\u0112'
+ shift+ralt, capslock+ralt: '\u0112'
+ shift+capslock+ralt: '\u0113'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
ralt: '\u0157'
- shift+ralt, ralt+capslock: '\u0156'
+ shift+ralt, capslock+ralt: '\u0156'
+ shift+capslock+ralt: '\u0157'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
ralt: '\u016b'
- shift+ralt, ralt+capslock: '\u016a'
+ shift+ralt, capslock+ralt: '\u016a'
+ shift+capslock+ralt: '\u016b'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u012b'
- shift+ralt, ralt+capslock: '\u012a'
+ shift+ralt, capslock+ralt: '\u012a'
+ shift+capslock+ralt: '\u012b'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f5'
- shift+ralt, ralt+capslock: '\u00d5'
+ shift+ralt, capslock+ralt: '\u00d5'
+ shift+capslock+ralt: '\u00f5'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -204,64 +219,78 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u0101'
- shift+ralt, ralt+capslock: '\u0100'
+ shift+ralt, capslock+ralt: '\u0100'
+ shift+capslock+ralt: '\u0101'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- shift+ralt, ralt+capslock: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: '\u0123'
- shift+ralt, ralt+capslock: '\u0122'
+ shift+ralt, capslock+ralt: '\u0122'
+ shift+capslock+ralt: '\u0123'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u0137'
- shift+ralt, ralt+capslock: '\u0136'
+ shift+ralt, capslock+ralt: '\u0136'
+ shift+capslock+ralt: '\u0137'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u013c'
- shift+ralt, ralt+capslock: '\u013b'
+ shift+ralt, capslock+ralt: '\u013b'
+ shift+capslock+ralt: '\u013c'
}
key SEMICOLON {
@@ -298,48 +327,58 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- shift+ralt, ralt+capslock: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u010d'
- shift+ralt, ralt+capslock: '\u010c'
+ shift+ralt, capslock+ralt: '\u010c'
+ shift+capslock+ralt: '\u010d'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u0146'
- shift+ralt, ralt+capslock: '\u0145'
+ shift+ralt, capslock+ralt: '\u0145'
+ shift+capslock+ralt: '\u0146'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
index 72ca333..bcfdb12 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
@@ -32,6 +32,7 @@
label: '1'
base: '\u0105'
shift, capslock: '\u0104'
+ shift+capslock: '\u0105'
ralt: '1'
shift+ralt: '!'
}
@@ -40,6 +41,7 @@
label: '2'
base: '\u010d'
shift, capslock: '\u010c'
+ shift+capslock: '\u010d'
ralt: '2'
shift+ralt: '@'
}
@@ -48,6 +50,7 @@
label: '3'
base: '\u0119'
shift, capslock: '\u0118'
+ shift+capslock: '\u0119'
ralt: '3'
shift+ralt: '#'
}
@@ -56,6 +59,7 @@
label: '4'
base: '\u0117'
shift, capslock: '\u0116'
+ shift+capslock: '\u0117'
ralt: '4'
shift+ralt: '$'
}
@@ -64,6 +68,7 @@
label: '5'
base: '\u012f'
shift, capslock: '\u012e'
+ shift+capslock: '\u012f'
ralt: '5'
shift+ralt: '%'
}
@@ -72,6 +77,7 @@
label: '6'
base: '\u0161'
shift, capslock: '\u0160'
+ shift+capslock: '\u0161'
ralt: '6'
shift+ralt: '\u0302'
}
@@ -80,6 +86,7 @@
label: '7'
base: '\u0173'
shift, capslock: '\u0172'
+ shift+capslock: '\u0173'
ralt: '7'
shift+ralt: '&'
}
@@ -88,6 +95,7 @@
label: '8'
base: '\u016b'
shift, capslock: '\u016a'
+ shift+capslock: '\u016b'
ralt: '8'
shift+ralt: '*'
}
@@ -116,6 +124,7 @@
label: '='
base: '\u017e'
shift, capslock: '\u017d'
+ shift+capslock: '\u017e'
ralt: '='
shift+ralt: '+'
}
@@ -126,18 +135,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -145,42 +157,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -201,54 +220,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -281,42 +309,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
index 3d4a8c6..77cc672 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
@@ -28,6 +28,7 @@
label: '='
base: '='
shift, capslock: '+'
+ shift+capslock: '+'
ralt: '`'
ralt+shift: '~'
}
@@ -122,86 +123,107 @@
label: '\u0444'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0446'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0443'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u0436'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u044d'
base: '\u044d'
shift, capslock: '\u042d'
+ shift+capslock: '\u044d'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u043d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0433'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0448'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u04af'
base: '\u04af'
shift, capslock: '\u04ae'
+ shift+capslock: '\u04af'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0437'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u043a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: '['
ralt+shift: '{'
}
@@ -210,6 +232,7 @@
label: '\u044a'
base: '\u044a'
shift, capslock: '\u042a'
+ shift+capslock: '\u044a'
ralt: ']'
ralt+shift: '}'
}
@@ -220,78 +243,97 @@
label: '\u0439'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u044b'
base: '\u044b'
shift, capslock: '\u042b'
+ shift+capslock: '\u044b'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0431'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u04e9'
base: '\u04e9'
shift, capslock: '\u04e8'
+ shift+capslock: '\u04e9'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u0430'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0445'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u0440'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u043e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u043b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u0434'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: ';'
ralt+shift: ':'
}
@@ -300,6 +342,7 @@
label: '\u043f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: '\''
ralt+shift: '"'
}
@@ -318,62 +361,77 @@
label: '\u044f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0447'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0451'
base: '\u0451'
shift, capslock: '\u0401'
+ shift+capslock: '\u0451'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u0441'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u043c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0438'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u0442'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u044c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: ','
ralt+shift: '<'
}
@@ -382,6 +440,7 @@
label: '\u0432'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: '.'
ralt+shift: '>'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
index 560dd16..cae1c94 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
@@ -115,76 +115,90 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\u00e2'
- ralt+capslock, shift+ralt: '\u00c2'
+ shift+ralt, capslock+ralt: '\u00c2'
+ shift+capslock+ralt: '\u00e2'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
- ralt+capslock: '\u20ac'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u0167'
- ralt+capslock, shift+ralt: '\u0166'
+ shift+ralt, capslock+ralt: '\u0166'
+ shift+capslock+ralt: '\u0167'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ef'
- ralt+capslock, shift+ralt: '\u00cf'
+ shift+ralt, capslock+ralt: '\u00cf'
+ shift+capslock+ralt: '\u00ef'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f5'
- ralt+capslock, shift+ralt: '\u00d5'
+ shift+ralt, capslock+ralt: '\u00d5'
+ shift+capslock+ralt: '\u00f5'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00c5'
base: '\u00e5'
shift, capslock: '\u00c5'
+ shift+capslock: '\u00e5'
}
key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- ralt+capslock, shift+ralt: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- ralt+capslock, shift+ralt: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0111'
- ralt+capslock, shift+ralt: '\u0110'
+ shift+ralt, capslock+ralt: '\u0110'
+ shift+capslock+ralt: '\u0111'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '\u01e5'
- ralt+capslock, shift+ralt: '\u01e4'
+ shift+ralt, capslock+ralt: '\u01e4'
+ shift+capslock+ralt: '\u01e5'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: '\u01e7'
- ralt+capslock, shift+ralt: '\u01e6'
+ shift+ralt, capslock+ralt: '\u01e6'
+ shift+capslock+ralt: '\u01e7'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u021f'
- ralt+capslock, shift+ralt: '\u021e'
+ shift+ralt, capslock+ralt: '\u021e'
+ shift+capslock+ralt: '\u021f'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u01e9'
- ralt+capslock, shift+ralt: '\u01e8'
+ shift+ralt, capslock+ralt: '\u01e8'
+ shift+capslock+ralt: '\u01e9'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d8'
base: '\u00f8'
shift, capslock: '\u00d8'
+ shift+capslock: '\u00f8'
ralt: '\u00f6'
- ralt+capslock, shift+ralt: '\u00d6'
+ shift+ralt, capslock+ralt: '\u00d6'
+ shift+capslock+ralt: '\u00f6'
}
key APOSTROPHE {
label: '\u00c6'
base: '\u00e6'
shift, capslock: '\u00c6'
+ shift+capslock: '\u00e6'
ralt: '\u00e4'
- ralt+capslock, shift+ralt: '\u00c4'
+ shift+ralt, capslock+ralt: '\u00c4'
+ shift+capslock+ralt: '\u00e4'
}
key BACKSLASH {
@@ -298,53 +332,65 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- ralt+capslock, shift+ralt: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u010d'
- ralt+capslock, shift+ralt: '\u010c'
+ shift+ralt, capslock+ralt: '\u010c'
+ shift+capslock+ralt: '\u010d'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '\u01ef'
- ralt+capslock, shift+ralt: '\u01ee'
+ shift+ralt, capslock+ralt: '\u01ee'
+ shift+capslock+ralt: '\u01ef'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '\u0292'
- ralt+capslock, shift+ralt: '\u01b7'
+ shift+ralt, capslock+ralt: '\u01b7'
+ shift+capslock+ralt: '\u0292'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u014b'
- ralt+capslock, shift+ralt: '\u014a'
+ shift+ralt, capslock+ralt: '\u014a'
+ shift+capslock+ralt: '\u014b'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
- ralt, ralt+capslock: '\u00b5'
+ shift+capslock: 'm'
+ ralt: '\u00b5'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
index bfe7821..6744922 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
@@ -22,231 +22,222 @@
label: '\u0634'
base: '\u0634'
shift, capslock: '\u0624'
- ctrl, alt, meta: none
+ shift+capslock: '\u0634'
}
key B {
label: '\u0630'
base: '\u0630'
shift, capslock: '\u200C'
- ctrl, alt, meta: none
+ shift+capslock: '\u0630'
}
key C {
label: '\u0632'
base: '\u0632'
shift, capslock: '\u0698'
- ctrl, alt, meta: none
+ shift+capslock: '\u0632'
}
key D {
label: '\u06CC'
base: '\u06CC'
shift, capslock: '\u064A'
- ctrl, alt, meta: none
+ shift+capslock: '\u06CC'
}
key E {
label: '\u062B'
base: '\u062B'
shift, capslock: '\u064D'
- ctrl, alt, meta: none
+ shift+capslock: '\u062B'
}
key F {
label: '\u0628'
base: '\u0628'
shift, capslock: '\u0625'
- ctrl, alt, meta: none
+ shift+capslock: '\u0628'
}
key G {
label: '\u0644'
base: '\u0644'
shift, capslock: '\u0623'
- ctrl, alt, meta: none
+ shift+capslock: '\u0644'
}
key H {
label: '\u0627'
base: '\u0627'
shift, capslock: '\u0622'
- ctrl, alt, meta: none
+ shift+capslock: '\u0627'
}
key I {
label: '\u0647'
base: '\u0647'
shift, capslock: '\u0651'
- ctrl, alt, meta: none
+ shift+capslock: '\u0647'
}
key J {
label: '\u062A'
base: '\u062A'
shift, capslock: '\u0629'
- ctrl, alt, meta: none
+ shift+capslock: '\u062A'
}
key K {
label: '\u0646'
base: '\u0646'
shift, capslock: '\u00AB'
- ctrl, alt, meta: none
+ shift+capslock: '\u0646'
}
key L {
label: '\u0645'
base: '\u0645'
shift, capslock: '\u00BB'
- ctrl, alt, meta: none
+ shift+capslock: '\u0645'
}
key M {
label: '\u067E'
base: '\u067E'
shift, capslock: '\u0621'
- ctrl, alt, meta: none
+ shift+capslock: '\u067E'
}
key N {
label: '\u062F'
base: '\u062F'
shift, capslock: '\u0654'
- ctrl, alt, meta: none
+ shift+capslock: '\u062F'
}
key O {
label: '\u062E'
base: '\u062E'
- shift, capslock: ']'
- ctrl, alt, meta: none
+ shift: ']'
}
key P {
label: '\u062D'
base: '\u062D'
- shift, capslock: '['
- ctrl, alt, meta: none
+ shift: '['
}
key Q {
label: '\u0636'
base: '\u0636'
shift, capslock: '\u0652'
- ctrl, alt, meta: none
+ shift+capslock: '\u0636'
}
key R {
label: '\u0642'
base: '\u0642'
shift, capslock: '\u064B'
- ctrl, alt, meta: none
+ shift+capslock: '\u0642'
}
key S {
label: '\u0633'
base: '\u0633'
shift, capslock: '\u0626'
- ctrl, alt, meta: none
+ shift+capslock: '\u0633'
}
key T {
label: '\u0641'
base: '\u0641'
shift, capslock: '\u064F'
- ctrl, alt, meta: none
+ shift+capslock: '\u0641'
}
key U {
label: '\u0639'
base: '\u0639'
shift, capslock: '\u064E'
- ctrl, alt, meta: none
+ shift+capslock: '\u0639'
}
key V {
label: '\u0631'
base: '\u0631'
shift, capslock: '\u0670'
- ctrl, alt, meta: none
+ shift+capslock: '\u0631'
}
key W {
label: '\u0635'
base: '\u0635'
shift, capslock: '\u064C'
- ctrl, alt, meta: none
+ shift+capslock: '\u0635'
}
key X {
label: '\u0637'
base: '\u0637'
shift, capslock: '\u0653'
- ctrl, alt, meta: none
+ shift+capslock: '\u0637'
}
key Y {
label: '\u063A'
base: '\u063A'
shift, capslock: '\u0650'
- ctrl, alt, meta: none
+ shift+capslock: '\u063A'
}
key Z {
label: '\u0638'
base: '\u0638'
shift, capslock: '\u0643'
- ctrl, alt, meta: none
+ shift+capslock: '\u0638'
}
key 0 {
label, number: '\u06F0'
base: '\u06F0'
shift: '('
- ctrl, alt, meta: none
}
key 1 {
label, number: '\u06F1'
base: '\u06F1'
shift: '!'
- ctrl, alt, meta: none
}
key 2 {
label, number: '\u06F2'
base: '\u06F2'
shift: '\u066C'
- ctrl, alt, meta: none
}
key 3 {
label, number: '\u06F3'
base: '\u06F3'
shift: '\u066B'
- ctrl, alt, meta: none
}
key 4 {
label, number: '\u06F4'
base: '\u06F4'
shift: '\uFDFC'
- ctrl, alt, meta: none
}
key 5 {
label, number: '\u06F5'
base: '\u06F5'
shift: '\u066A'
- ctrl, alt, meta: none
}
key 6 {
label, number: '\u06F6'
base: '\u06F6'
shift: '\u00D7'
- ctrl, alt, meta: none
}
@@ -254,248 +245,82 @@
label, number: '\u06F7'
base: '\u06F7'
shift: '\u060C'
- ctrl, alt, meta: none
}
key 8 {
label, number: '\u06F8'
base: '\u06F8'
shift: '*'
- ctrl, alt, meta: none
}
key 9 {
label, number: '\u06F9'
base: '\u06F9'
shift: ')'
- ctrl, alt, meta: none
-}
-
-key SPACE {
- label: ' '
- base: ' '
- ctrl, alt, meta: none
-}
-
-key ENTER {
- label: '\n'
- base: '\n'
- ctrl, alt, meta: none
-}
-
-key TAB {
- label: '\t'
- base: '\t'
- ctrl, alt, meta: none
}
key COMMA {
label, number: '\u0648'
base: '\u0648'
shift: '>'
- ctrl, alt, meta: none
}
key PERIOD {
label, number: '.'
base: '.'
shift: '<'
- ctrl, alt, meta: none
}
key SLASH {
label, number: '/'
base: '/'
shift: '\u061F'
- ctrl, alt, meta: none
}
key GRAVE {
label, number: '`'
base: '`'
shift: '\u00F7'
- ctrl, alt, meta: none
}
-
key MINUS {
label, number: '-'
base: '-'
shift: '_'
- ctrl, alt, meta: none
}
key EQUALS {
label, number: '='
base: '='
shift: '+'
- ctrl, alt, meta: none
}
key LEFT_BRACKET {
label, number: '\u062C'
base: '\u062C'
shift: '}'
- ctrl, alt, meta: none
}
key RIGHT_BRACKET {
label, number: '\u0686'
base: '\u0686'
shift: '{'
- ctrl, alt, meta: none
}
key BACKSLASH {
label, number: '\\'
base: '\\'
shift: '|'
- ctrl, alt, meta: none
}
key SEMICOLON {
label, number: '\u06A9'
base: '\u06A9'
shift: ':'
- ctrl, alt, meta: none
}
key APOSTROPHE {
label, number: '\''
base: '\''
shift: '\"'
- ctrl, alt, meta: none
-}
-
-### Numeric keypad ###
-
-key NUMPAD_0 {
- label, number: '0'
- base: fallback INSERT
- numlock: '0'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_1 {
- label, number: '1'
- base: fallback MOVE_END
- numlock: '1'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_2 {
- label, number: '2'
- base: fallback DPAD_DOWN
- numlock: '2'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_3 {
- label, number: '3'
- base: fallback PAGE_DOWN
- numlock: '3'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_4 {
- label, number: '4'
- base: fallback DPAD_LEFT
- numlock: '4'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_5 {
- label, number: '5'
- base: fallback DPAD_CENTER
- numlock: '5'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_6 {
- label, number: '6'
- base: fallback DPAD_RIGHT
- numlock: '6'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_7 {
- label, number: '7'
- base: fallback MOVE_HOME
- numlock: '7'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_8 {
- label, number: '8'
- base: fallback DPAD_UP
- numlock: '8'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_9 {
- label, number: '9'
- base: fallback PAGE_UP
- numlock: '9'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_LEFT_PAREN {
- label, number: ')'
- base: ')'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_RIGHT_PAREN {
- label, number: '('
- base: '('
- ctrl, alt, meta: none
-}
-
-key NUMPAD_DIVIDE {
- label, number: '/'
- base: '/'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_MULTIPLY {
- label, number: '*'
- base: '*'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_SUBTRACT {
- label, number: '-'
- base: '-'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_ADD {
- label, number: '+'
- base: '+'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_DOT {
- label, number: '.'
- base: fallback FORWARD_DEL
- numlock: '.'
- ctrl, alt, meta: none
-}
-
-key NUMPAD_COMMA {
- label, number: ','
- base: ','
- ctrl, alt, meta: none
-}
-
-key NUMPAD_EQUALS {
- label, number: '='
- base: '='
- ctrl, alt, meta: none
-}
-
-key NUMPAD_ENTER {
- label: '\n'
- base: '\n' fallback ENTER
- ctrl, alt, meta: none fallback ENTER
-}
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
index 559ec07..66fbefc1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
@@ -104,64 +104,76 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u0119'
- ralt+shift, ralt+capslock: '\u0118'
+ shift+ralt, capslock+ralt: '\u0118'
+ shift+capslock+ralt: '\u0119'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00F3'
- ralt+shift, ralt+capslock: '\u00D3'
+ shift+ralt, capslock+ralt: '\u00D3'
+ shift+capslock+ralt: '\u00F3'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -188,60 +200,72 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u0105'
- ralt+shift, ralt+capslock: '\u0104'
+ shift+ralt, capslock+ralt: '\u0104'
+ shift+capslock+ralt: '\u0105'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u015b'
- ralt+shift, ralt+capslock: '\u015a'
+ shift+ralt, capslock+ralt: '\u015a'
+ shift+capslock+ralt: '\u015b'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u0142'
- ralt+shift, ralt+capslock: '\u0141'
+ shift+ralt, capslock+ralt: '\u0141'
+ shift+capslock+ralt: '\u0142'
}
key SEMICOLON {
@@ -262,50 +286,61 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017c'
- ralt+shift, ralt+capslock: '\u017b'
+ shift+ralt, capslock+ralt: '\u017b'
+ shift+capslock+ralt: '\u017c'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
ralt: '\u017a'
- ralt+shift, ralt+capslock: '\u0179'
+ shift+ralt, capslock+ralt: '\u0179'
+ shift+capslock+ralt: '\u017a'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u0107'
- ralt+shift, ralt+capslock: '\u0106'
+ shift+ralt, capslock+ralt: '\u0106'
+ shift+capslock+ralt: '\u0107'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u0144'
- ralt+shift, ralt+capslock: '\u0143'
+ shift+ralt, capslock+ralt: '\u0143'
+ shift+capslock+ralt: '\u0144'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
index 47ee867..6fe0e47 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
@@ -115,18 +115,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -134,42 +137,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -191,60 +201,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00c7'
base: '\u00e7'
shift, capslock: '\u00c7'
+ shift+capslock: '\u00e7'
}
key APOSTROPHE {
@@ -272,42 +292,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
index 41c6bb3..ecada49 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
@@ -28,6 +28,7 @@
label: '\u0401'
base: '\u0451'
shift, capslock: '\u0401'
+ shift+capslock: '\u0451'
ralt: '`'
ralt+shift: '~'
}
@@ -124,86 +125,107 @@
label: '\u0419'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0426'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0423'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u041a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0415'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u041d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0413'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0428'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u0429'
base: '\u0449'
shift, capslock: '\u0429'
+ shift+capslock: '\u0449'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0417'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u0425'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: '['
ralt+shift: '{'
}
@@ -212,6 +234,7 @@
label: '\u042a'
base: '\u044a'
shift, capslock: '\u042a'
+ shift+capslock: '\u044a'
ralt: ']'
ralt+shift: '}'
}
@@ -222,78 +245,97 @@
label: '\u0424'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u042b'
base: '\u044b'
shift, capslock: '\u042b'
+ shift+capslock: '\u044b'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0412'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u0410'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u041f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0420'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u041e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u041b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u0414'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u0416'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: ';'
ralt+shift: ':'
}
@@ -302,6 +344,7 @@
label: '\u042d'
base: '\u044d'
shift, capslock: '\u042d'
+ shift+capslock: '\u044d'
ralt: '\''
ralt+shift: '"'
}
@@ -319,62 +362,77 @@
label: '\u042f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0427'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0421'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u041c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0418'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0422'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u042c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u0411'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: ','
ralt+shift: '<'
}
@@ -383,6 +441,7 @@
label: '\u042e'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: '.'
ralt+shift: '>'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
index 11c2ad4..5417bc3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
@@ -126,86 +126,107 @@
label: '\u0419'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0426'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0423'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u041a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0415'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u041d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0413'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0428'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u0429'
base: '\u0449'
shift, capslock: '\u0429'
+ shift+capslock: '\u0449'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0417'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u0425'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: '['
ralt+shift: '{'
}
@@ -214,6 +235,7 @@
label: '\u042a'
base: '\u044a'
shift, capslock: '\u042a'
+ shift+capslock: '\u044a'
ralt: ']'
ralt+shift: '}'
}
@@ -224,78 +246,97 @@
label: '\u0424'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u042b'
base: '\u044b'
shift, capslock: '\u042b'
+ shift+capslock: '\u044b'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0412'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u0410'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u041f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0420'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u041e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u041b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u0414'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u0416'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: ';'
ralt+shift: ':'
}
@@ -304,6 +345,7 @@
label: '\u042d'
base: '\u044d'
shift, capslock: '\u042d'
+ shift+capslock: '\u044d'
ralt: '\''
ralt+shift: '"'
}
@@ -312,6 +354,7 @@
label: '\u0401'
base: '\u0451'
shift, capslock: '\u0401'
+ shift+capslock: '\u0451'
ralt: '\\'
ralt+shift: '|'
}
@@ -330,62 +373,77 @@
label: '\u042f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0427'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0421'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u041c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0418'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0422'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u042c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u0411'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: ','
ralt+shift: '<'
}
@@ -394,6 +452,7 @@
label: '\u042e'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: '.'
ralt+shift: '>'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
index 2eb0f63..5065aa8 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
@@ -118,6 +118,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\\'
}
@@ -125,6 +126,7 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '|'
}
@@ -132,6 +134,7 @@
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -139,42 +142,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
ralt: '\''
}
@@ -198,12 +208,14 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0111'
}
@@ -211,6 +223,7 @@
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0110'
}
@@ -218,6 +231,7 @@
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '['
}
@@ -225,6 +239,7 @@
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: ']'
}
@@ -232,18 +247,21 @@
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u0142'
}
@@ -251,6 +269,7 @@
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
ralt: '\u0141'
}
@@ -288,6 +307,7 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '>'
}
@@ -295,6 +315,7 @@
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
ralt: '#'
}
@@ -302,6 +323,7 @@
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '&'
}
@@ -309,6 +331,7 @@
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '@'
}
@@ -316,6 +339,7 @@
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '{'
}
@@ -323,6 +347,7 @@
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '}'
}
@@ -330,6 +355,7 @@
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
index da9159b..6a63e70 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
@@ -113,18 +113,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -132,42 +135,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -190,60 +200,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d1'
base: '\u00f1'
shift, capslock: '\u00d1'
+ shift+capslock: '\u00f1'
}
key APOSTROPHE {
@@ -257,6 +277,7 @@
label: '\u00c7'
base: '\u00e7'
shift, capslock: '\u00c7'
+ shift+capslock: '\u00e7'
ralt: '}'
}
@@ -272,42 +293,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
index 16eb53f..29aab97 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
@@ -109,6 +109,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '@'
}
@@ -116,54 +117,63 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -186,60 +196,70 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d1'
base: '\u00f1'
shift, capslock: '\u00d1'
+ shift+capslock: '\u00f1'
}
key APOSTROPHE {
@@ -268,42 +288,49 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
index 8a4e9a5..f12804f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
@@ -115,76 +115,90 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '\u00e2'
- ralt+capslock, shift+ralt: '\u00c2'
+ shift+ralt, capslock+ralt: '\u00c2'
+ shift+capslock+ralt: '\u00e2'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
- ralt+capslock: '\u20ac'
}
key R {
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u0167'
- ralt+capslock, shift+ralt: '\u0166'
+ shift+ralt, capslock+ralt: '\u0166'
+ shift+capslock+ralt: '\u0167'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00ef'
- ralt+capslock, shift+ralt: '\u00cf'
+ shift+ralt, capslock+ralt: '\u00cf'
+ shift+capslock+ralt: '\u00ef'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
ralt: '\u00f5'
- ralt+capslock, shift+ralt: '\u00d5'
+ shift+ralt, capslock+ralt: '\u00d5'
+ shift+capslock+ralt: '\u00f5'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u00c5'
base: '\u00e5'
shift, capslock: '\u00c5'
+ shift+capslock: '\u00e5'
}
key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e1'
- ralt+capslock, shift+ralt: '\u00c1'
+ shift+ralt, capslock+ralt: '\u00c1'
+ shift+capslock+ralt: '\u00e1'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u0161'
- ralt+capslock, shift+ralt: '\u0160'
+ shift+ralt, capslock+ralt: '\u0160'
+ shift+capslock+ralt: '\u0161'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u0111'
- ralt+capslock, shift+ralt: '\u0110'
+ shift+ralt, capslock+ralt: '\u0110'
+ shift+capslock+ralt: '\u0111'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '\u01e5'
- ralt+capslock, shift+ralt: '\u01e4'
+ shift+ralt, capslock+ralt: '\u01e4'
+ shift+capslock+ralt: '\u01e5'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
ralt: '\u01e7'
- ralt+capslock, shift+ralt: '\u01e6'
+ shift+ralt, capslock+ralt: '\u01e6'
+ shift+capslock+ralt: '\u01e7'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u021f'
- ralt+capslock, shift+ralt: '\u021e'
+ shift+ralt, capslock+ralt: '\u021e'
+ shift+capslock+ralt: '\u021f'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
ralt: '\u01e9'
- ralt+capslock, shift+ralt: '\u01e8'
+ shift+ralt, capslock+ralt: '\u01e8'
+ shift+capslock+ralt: '\u01e9'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
ralt: '\u00f8'
- ralt+capslock, shift+ralt: '\u00d8'
+ shift+ralt, capslock+ralt: '\u00d8'
+ shift+capslock+ralt: '\u00f8'
}
key APOSTROPHE {
label: '\u00c4'
base: '\u00e4'
shift, capslock: '\u00c4'
+ shift+capslock: '\u00e4'
ralt: '\u00e6'
- ralt+capslock, shift+ralt: '\u00c6'
+ shift+ralt, capslock+ralt: '\u00c6'
+ shift+capslock+ralt: '\u00e6'
}
key BACKSLASH {
@@ -299,53 +333,65 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
ralt: '\u017e'
- ralt+capslock, shift+ralt: '\u017d'
+ shift+ralt, capslock+ralt: '\u017d'
+ shift+capslock+ralt: '\u017e'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
ralt: '\u010d'
- ralt+capslock, shift+ralt: '\u010c'
+ shift+ralt, capslock+ralt: '\u010c'
+ shift+capslock+ralt: '\u010d'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '\u01ef'
- ralt+capslock, shift+ralt: '\u01ee'
+ shift+ralt, capslock+ralt: '\u01ee'
+ shift+capslock+ralt: '\u01ef'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '\u0292'
- ralt+capslock, shift+ralt: '\u01b7'
+ shift+ralt, capslock+ralt: '\u01b7'
+ shift+capslock+ralt: '\u0292'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
ralt: '\u014b'
- ralt+capslock, shift+ralt: '\u014a'
+ shift+ralt, capslock+ralt: '\u014a'
+ shift+capslock+ralt: '\u014b'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
- ralt, ralt+capslock: '\u00b5'
+ shift+capslock: 'm'
+ ralt: '\u00b5'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
index 9e20462..6476793 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
@@ -119,18 +119,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -138,42 +141,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -196,54 +206,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -279,42 +298,49 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
index 7fbd1a9..9d6f367 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
@@ -119,18 +119,21 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
}
key W {
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -138,42 +141,49 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Z {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: 'i'
shift, capslock: 'I'
+ shift+capslock: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
@@ -198,54 +208,63 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
}
key D {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
@@ -285,42 +304,49 @@
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
index e193d34..2a8fcef 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
@@ -124,6 +124,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '@'
}
@@ -131,12 +132,14 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
}
key E {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -144,50 +147,59 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key T {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
}
key Y {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
}
key U {
label: 'U'
base: 'u'
shift, capslock: 'U'
+ shift+capslock: 'u'
}
key I {
label: 'I'
base: '\u0131'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: 'i'
- ralt+shift, ralt+capslock: '\u0130'
+ shift+ralt, capslock+ralt: '\u0130'
+ shift+capslock+ralt: 'i'
}
key O {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
}
key LEFT_BRACKET {
label: '\u011e'
base: '\u011f'
shift, capslock: '\u011e'
+ shift+capslock: '\u011f'
ralt: '\u0308'
}
@@ -195,6 +207,7 @@
label: '\u00dc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
ralt: '\u0303'
}
@@ -204,14 +217,17 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00e6'
- ralt+shift, ralt+capslock: '\u00c6'
+ shift+ralt, capslock+ralt: '\u00c6'
+ shift+capslock+ralt: '\u00e6'
}
key S {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u00df'
}
@@ -219,48 +235,56 @@
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
}
key F {
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
}
key G {
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key H {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
}
key J {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
}
key K {
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: '\u015e'
base: '\u015f'
shift, capslock: '\u015e'
+ shift+capslock: '\u015f'
ralt: '\u0301'
}
@@ -268,6 +292,7 @@
label: '\u0130'
base: 'i'
shift, capslock: '\u0130'
+ shift+capslock: 'i'
}
key COMMA {
@@ -290,54 +315,63 @@
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key X {
label: 'X'
base: 'x'
shift, capslock: 'X'
+ shift+capslock: 'x'
}
key C {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key V {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
}
key B {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
}
key N {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key M {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key EQUALS {
label: '\u00d6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
}
key BACKSLASH {
label: '\u00c7'
base: '\u00e7'
shift, capslock: '\u00c7'
+ shift+capslock: '\u00e7'
}
key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
index 5b96da0..b27f6fa 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
@@ -125,6 +125,7 @@
label: 'F'
base: 'f'
shift, capslock: 'F'
+ shift+capslock: 'f'
ralt: '@'
}
@@ -132,32 +133,38 @@
label: 'G'
base: 'g'
shift, capslock: 'G'
+ shift+capslock: 'g'
}
key E {
label: '\u011f'
base: '\u011f'
shift, capslock: '\u011e'
+ shift+capslock: '\u011f'
}
key R {
label: '\u0131'
base: '\u0131'
shift, capslock: 'I'
+ shift+capslock: 'i'
ralt: '\u00b6'
- ralt+shift, ralt+capslock: '\u00ae'
+ shift+ralt, capslock+ralt: '\u00ae'
+ shift+capslock+ralt: '\u00b6'
}
key T {
label: 'O'
base: 'o'
shift, capslock: 'O'
+ shift+capslock: 'o'
}
key Y {
label: 'D'
base: 'd'
shift, capslock: 'D'
+ shift+capslock: 'd'
ralt: '\u00a5'
}
@@ -165,26 +172,31 @@
label: 'R'
base: 'r'
shift, capslock: 'R'
+ shift+capslock: 'r'
}
key I {
label: 'N'
base: 'n'
shift, capslock: 'N'
+ shift+capslock: 'n'
}
key O {
label: 'H'
base: 'h'
shift, capslock: 'H'
+ shift+capslock: 'h'
ralt: '\u00f8'
- ralt+shift, ralt+capslock: '\u00d8'
+ shift+ralt, capslock+ralt: '\u00d8'
+ shift+capslock+ralt: '\u00f8'
}
key P {
label: 'P'
base: 'p'
shift, capslock: 'P'
+ shift+capslock: 'p'
ralt: '\u00a3'
}
@@ -192,6 +204,7 @@
label: 'Q'
base: 'q'
shift, capslock: 'Q'
+ shift+capslock: 'q'
ralt: '"'
}
@@ -199,6 +212,7 @@
label: 'W'
base: 'w'
shift, capslock: 'W'
+ shift+capslock: 'w'
ralt: '~'
}
@@ -208,22 +222,27 @@
label: '\u0075'
base: '\u0075'
shift, capslock: '\u0055'
+ shift+capslock: '\u0075'
ralt: '\u00e6'
- ralt+shift, ralt+capslock: '\u00c6'
+ shift+ralt, capslock+ralt: '\u00c6'
+ shift+capslock+ralt: '\u00e6'
}
key S {
label: 'i'
base: 'i'
shift, capslock: '\u0130'
+ shift+capslock: 'i'
ralt: '\u00df'
- ralt+shift, ralt+capslock: '\u00a7'
+ shift+ralt, capslock+ralt: '\u00a7'
+ shift+capslock+ralt: '\u00df'
}
key D {
label: 'E'
base: 'e'
shift, capslock: 'E'
+ shift+capslock: 'e'
ralt: '\u20ac'
}
@@ -231,6 +250,7 @@
label: 'A'
base: 'a'
shift, capslock: 'A'
+ shift+capslock: 'a'
ralt: '\u00aa'
}
@@ -238,12 +258,14 @@
label: '\u00fc'
base: '\u00fc'
shift, capslock: '\u00dc'
+ shift+capslock: '\u00fc'
}
key H {
label: 'T'
base: 't'
shift, capslock: 'T'
+ shift+capslock: 't'
ralt: '\u20ba'
}
@@ -251,24 +273,28 @@
label: 'K'
base: 'k'
shift, capslock: 'K'
+ shift+capslock: 'k'
}
key K {
label: 'M'
base: 'm'
shift, capslock: 'M'
+ shift+capslock: 'm'
}
key L {
label: 'L'
base: 'l'
shift, capslock: 'L'
+ shift+capslock: 'l'
}
key SEMICOLON {
label: 'Y'
base: 'y'
shift, capslock: 'Y'
+ shift+capslock: 'y'
ralt: '\u00b4'
}
@@ -276,6 +302,7 @@
label: '\u015f'
base: '\u015f'
shift, capslock: '\u015e'
+ shift+capslock: '\u015f'
}
key COMMA {
@@ -292,63 +319,76 @@
base: '<'
shift: '>'
ralt: '|'
- ralt+shift, ralt+capslock: '\u00a6'
+ shift+ralt, capslock+ralt: '\u00a6'
+ shift+capslock+ralt: '|'
}
key Z {
label: 'J'
base: 'j'
shift, capslock: 'J'
+ shift+capslock: 'j'
ralt: '\u00ab'
- ralt+shift, ralt+capslock: '<'
+ shift+ralt, capslock+ralt: '<'
+ shift+capslock+ralt: '\u00ab'
}
key X {
label: '\u00f6'
base: '\u00f6'
shift, capslock: '\u00d6'
+ shift+capslock: '\u00f6'
ralt: '\u00bb'
- ralt+shift, ralt+capslock: '>'
+ shift+ralt, capslock+ralt: '>'
+ shift+capslock+ralt: '\u00bb'
}
key C {
label: 'V'
base: 'v'
shift, capslock: 'V'
+ shift+capslock: 'v'
ralt: '\u00a2'
- ralt+shift, ralt+capslock: '\u00a9'
+ shift+ralt, capslock+ralt: '\u00a9'
+ shift+capslock+ralt: '\u00a2'
}
key V {
label: 'C'
base: 'c'
shift, capslock: 'C'
+ shift+capslock: 'c'
}
key B {
label: '\u00e7'
base: '\u00e7'
shift, capslock: '\u00c7'
+ shift+capslock: '\u00e7'
}
key N {
label: 'Z'
base: 'z'
shift, capslock: 'Z'
+ shift+capslock: 'z'
}
key M {
label: 'S'
base: 's'
shift, capslock: 'S'
+ shift+capslock: 's'
ralt: '\u00b5'
- ralt+shift, ralt+capslock: '\u00ba'
+ shift+ralt, capslock+ralt: '\u00ba'
+ shift+capslock+ralt: '\u00b5'
}
key EQUALS {
label: 'B'
base: 'b'
shift, capslock: 'B'
+ shift+capslock: 'b'
ralt: '\u00d7'
}
@@ -356,6 +396,7 @@
label: '.'
base: '.'
shift, capslock: ':'
+ shift+capslock: ':'
ralt: '\u00f7'
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
index a802460..1346bbb 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
@@ -28,6 +28,7 @@
label: '\u0401'
base: '\u0451'
shift, capslock: '\u0401'
+ shift+capslock: '\u0451'
ralt: '`'
ralt+shift: '~'
}
@@ -124,86 +125,107 @@
label: '\u0419'
base: '\u0439'
shift, capslock: '\u0419'
+ shift+capslock: '\u0439'
ralt: 'q'
- ralt+shift, ralt+capslock: 'Q'
+ shift+ralt, capslock+ralt: 'Q'
+ shift+capslock+ralt: 'q'
}
key W {
label: '\u0426'
base: '\u0446'
shift, capslock: '\u0426'
+ shift+capslock: '\u0446'
ralt: 'w'
- ralt+shift, ralt+capslock: 'W'
+ shift+ralt, capslock+ralt: 'W'
+ shift+capslock+ralt: 'w'
}
key E {
label: '\u0423'
base: '\u0443'
shift, capslock: '\u0423'
+ shift+capslock: '\u0443'
ralt: 'e'
- ralt+shift, ralt+capslock: 'E'
+ shift+ralt, capslock+ralt: 'E'
+ shift+capslock+ralt: 'e'
}
key R {
label: '\u041a'
base: '\u043a'
shift, capslock: '\u041a'
+ shift+capslock: '\u043a'
ralt: 'r'
- ralt+shift, ralt+capslock: 'R'
+ shift+ralt, capslock+ralt: 'R'
+ shift+capslock+ralt: 'r'
}
key T {
label: '\u0415'
base: '\u0435'
shift, capslock: '\u0415'
+ shift+capslock: '\u0435'
ralt: 't'
- ralt+shift, ralt+capslock: 'T'
+ shift+ralt, capslock+ralt: 'T'
+ shift+capslock+ralt: 't'
}
key Y {
label: '\u041d'
base: '\u043d'
shift, capslock: '\u041d'
+ shift+capslock: '\u043d'
ralt: 'y'
- ralt+shift, ralt+capslock: 'Y'
+ shift+ralt, capslock+ralt: 'Y'
+ shift+capslock+ralt: 'y'
}
key U {
label: '\u0413'
base: '\u0433'
shift, capslock: '\u0413'
+ shift+capslock: '\u0433'
ralt: 'u'
- ralt+shift, ralt+capslock: 'U'
+ shift+ralt, capslock+ralt: 'U'
+ shift+capslock+ralt: 'u'
}
key I {
label: '\u0428'
base: '\u0448'
shift, capslock: '\u0428'
+ shift+capslock: '\u0448'
ralt: 'i'
- ralt+shift, ralt+capslock: 'I'
+ shift+ralt, capslock+ralt: 'I'
+ shift+capslock+ralt: 'i'
}
key O {
label: '\u0429'
base: '\u0449'
shift, capslock: '\u0429'
+ shift+capslock: '\u0449'
ralt: 'o'
- ralt+shift, ralt+capslock: 'O'
+ shift+ralt, capslock+ralt: 'O'
+ shift+capslock+ralt: 'o'
}
key P {
label: '\u0417'
base: '\u0437'
shift, capslock: '\u0417'
+ shift+capslock: '\u0437'
ralt: 'p'
- ralt+shift, ralt+capslock: 'P'
+ shift+ralt, capslock+ralt: 'P'
+ shift+capslock+ralt: 'p'
}
key LEFT_BRACKET {
label: '\u0425'
base: '\u0445'
shift, capslock: '\u0425'
+ shift+capslock: '\u0445'
ralt: '['
ralt+shift: '{'
}
@@ -212,6 +234,7 @@
label: '\u0407'
base: '\u0457'
shift, capslock: '\u0407'
+ shift+capslock: '\u0457'
ralt: ']'
ralt+shift: '}'
}
@@ -222,78 +245,97 @@
label: '\u0424'
base: '\u0444'
shift, capslock: '\u0424'
+ shift+capslock: '\u0444'
ralt: 'a'
- ralt+shift, ralt+capslock: 'A'
+ shift+ralt, capslock+ralt: 'A'
+ shift+capslock+ralt: 'a'
}
key S {
label: '\u0406'
base: '\u0456'
shift, capslock: '\u0406'
+ shift+capslock: '\u0456'
ralt: 's'
- ralt+shift, ralt+capslock: 'S'
+ shift+ralt, capslock+ralt: 'S'
+ shift+capslock+ralt: 's'
}
key D {
label: '\u0412'
base: '\u0432'
shift, capslock: '\u0412'
+ shift+capslock: '\u0432'
ralt: 'd'
- ralt+shift, ralt+capslock: 'D'
+ shift+ralt, capslock+ralt: 'D'
+ shift+capslock+ralt: 'd'
}
key F {
label: '\u0410'
base: '\u0430'
shift, capslock: '\u0410'
+ shift+capslock: '\u0430'
ralt: 'f'
- ralt+shift, ralt+capslock: 'F'
+ shift+ralt, capslock+ralt: 'F'
+ shift+capslock+ralt: 'f'
}
key G {
label: '\u041f'
base: '\u043f'
shift, capslock: '\u041f'
+ shift+capslock: '\u043f'
ralt: 'g'
- ralt+shift, ralt+capslock: 'G'
+ shift+ralt, capslock+ralt: 'G'
+ shift+capslock+ralt: 'g'
}
key H {
label: '\u0420'
base: '\u0440'
shift, capslock: '\u0420'
+ shift+capslock: '\u0440'
ralt: 'h'
- ralt+shift, ralt+capslock: 'H'
+ shift+ralt, capslock+ralt: 'H'
+ shift+capslock+ralt: 'h'
}
key J {
label: '\u041e'
base: '\u043e'
shift, capslock: '\u041e'
+ shift+capslock: '\u043e'
ralt: 'j'
- ralt+shift, ralt+capslock: 'J'
+ shift+ralt, capslock+ralt: 'J'
+ shift+capslock+ralt: 'j'
}
key K {
label: '\u041b'
base: '\u043b'
shift, capslock: '\u041b'
+ shift+capslock: '\u043b'
ralt: 'k'
- ralt+shift, ralt+capslock: 'K'
+ shift+ralt, capslock+ralt: 'K'
+ shift+capslock+ralt: 'k'
}
key L {
label: '\u0414'
base: '\u0434'
shift, capslock: '\u0414'
+ shift+capslock: '\u0434'
ralt: 'l'
- ralt+shift, ralt+capslock: 'L'
+ shift+ralt, capslock+ralt: 'L'
+ shift+capslock+ralt: 'l'
}
key SEMICOLON {
label: '\u0416'
base: '\u0436'
shift, capslock: '\u0416'
+ shift+capslock: '\u0436'
ralt: ';'
ralt+shift: ':'
}
@@ -302,6 +344,7 @@
label: '\u0404'
base: '\u0454'
shift, capslock: '\u0404'
+ shift+capslock: '\u0454'
ralt: '\''
ralt+shift: '"'
}
@@ -319,6 +362,7 @@
label: '\u0490'
base: '\u0491'
shift, capslock: '\u0490'
+ shift+capslock: '\u0491'
ralt: '\\'
ralt+shift: '|'
}
@@ -327,62 +371,77 @@
label: '\u042f'
base: '\u044f'
shift, capslock: '\u042f'
+ shift+capslock: '\u044f'
ralt: 'z'
- ralt+shift, ralt+capslock: 'Z'
+ shift+ralt, capslock+ralt: 'Z'
+ shift+capslock+ralt: 'z'
}
key X {
label: '\u0427'
base: '\u0447'
shift, capslock: '\u0427'
+ shift+capslock: '\u0447'
ralt: 'x'
- ralt+shift, ralt+capslock: 'X'
+ shift+ralt, capslock+ralt: 'X'
+ shift+capslock+ralt: 'x'
}
key C {
label: '\u0421'
base: '\u0441'
shift, capslock: '\u0421'
+ shift+capslock: '\u0441'
ralt: 'c'
- ralt+shift, ralt+capslock: 'C'
+ shift+ralt, capslock+ralt: 'C'
+ shift+capslock+ralt: 'c'
}
key V {
label: '\u041c'
base: '\u043c'
shift, capslock: '\u041c'
+ shift+capslock: '\u043c'
ralt: 'v'
- ralt+shift, ralt+capslock: 'V'
+ shift+ralt, capslock+ralt: 'V'
+ shift+capslock+ralt: 'v'
}
key B {
label: '\u0418'
base: '\u0438'
shift, capslock: '\u0418'
+ shift+capslock: '\u0438'
ralt: 'b'
- ralt+shift, ralt+capslock: 'B'
+ shift+ralt, capslock+ralt: 'B'
+ shift+capslock+ralt: 'b'
}
key N {
label: '\u0422'
base: '\u0442'
shift, capslock: '\u0422'
+ shift+capslock: '\u0442'
ralt: 'n'
- ralt+shift, ralt+capslock: 'N'
+ shift+ralt, capslock+ralt: 'N'
+ shift+capslock+ralt: 'n'
}
key M {
label: '\u042c'
base: '\u044c'
shift, capslock: '\u042c'
+ shift+capslock: '\u044c'
ralt: 'm'
- ralt+shift, ralt+capslock: 'M'
+ shift+ralt, capslock+ralt: 'M'
+ shift+capslock+ralt: 'm'
}
key COMMA {
label: '\u0411'
base: '\u0431'
shift, capslock: '\u0411'
+ shift+capslock: '\u0431'
ralt: ','
ralt+shift: '<'
}
@@ -391,6 +450,7 @@
label: '\u042e'
base: '\u044e'
shift, capslock: '\u042e'
+ shift+capslock: '\u044e'
ralt: '.'
ralt+shift: '>'
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index ca88f8d..215f6b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.framework.common
-import android.app.settings.SettingsEnums
import android.os.Bundle
// Defines the category of the log, for quick filter
@@ -32,14 +31,14 @@
}
// Defines the log events in Spa.
-enum class LogEvent(val action: Int) {
+enum class LogEvent {
// Page related events.
- PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
- PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
+ PAGE_ENTER,
+ PAGE_LEAVE,
// Entry related events.
- ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
- ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+ ENTRY_CLICK,
+ ENTRY_SWITCH,
}
internal const val LOG_DATA_DISPLAY_NAME = "name"
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index bd9e760..c8bcabf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.function.Predicate;
/**
* Class for managing services matching a given intent and requesting a given permission.
@@ -51,12 +52,13 @@
private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
private final List<ServiceInfo> mServices = new ArrayList<>();
private final List<Callback> mCallbacks = new ArrayList<>();
+ private final Predicate mValidator;
private boolean mListening;
private ServiceListing(Context context, String tag,
String setting, String intentAction, String permission, String noun,
- boolean addDeviceLockedFlags) {
+ boolean addDeviceLockedFlags, Predicate validator) {
mContentResolver = context.getContentResolver();
mContext = context;
mTag = tag;
@@ -65,6 +67,7 @@
mPermission = permission;
mNoun = noun;
mAddDeviceLockedFlags = addDeviceLockedFlags;
+ mValidator = validator;
}
public void addCallback(Callback callback) {
@@ -137,7 +140,6 @@
final PackageManager pmWrapper = mContext.getPackageManager();
List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
new Intent(mIntentAction), flags, user);
-
for (ResolveInfo resolveInfo : installedServices) {
ServiceInfo info = resolveInfo.serviceInfo;
@@ -148,6 +150,9 @@
+ mPermission);
continue;
}
+ if (mValidator != null && !mValidator.test(info)) {
+ continue;
+ }
mServices.add(info);
}
for (Callback callback : mCallbacks) {
@@ -194,6 +199,7 @@
private String mPermission;
private String mNoun;
private boolean mAddDeviceLockedFlags = false;
+ private Predicate mValidator;
public Builder(Context context) {
mContext = context;
@@ -224,6 +230,11 @@
return this;
}
+ public Builder setValidator(Predicate<ServiceInfo> validator) {
+ mValidator = validator;
+ return this;
+ }
+
/**
* Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
* MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
@@ -236,7 +247,7 @@
public ServiceListing build() {
return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
- mAddDeviceLockedFlags);
+ mAddDeviceLockedFlags, mValidator);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 688fc72..c4f09ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -31,13 +31,15 @@
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@@ -116,7 +118,7 @@
private final boolean mDreamsActivatedOnSleepByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
private final Set<ComponentName> mDisabledDreams;
- private final Set<Integer> mSupportedComplications;
+ private Set<Integer> mSupportedComplications;
private static DreamBackend sInstance;
public static DreamBackend getInstance(Context context) {
@@ -281,7 +283,18 @@
/** Gets all complications which have been enabled by the user. */
public Set<Integer> getEnabledComplications() {
- return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet();
+ final Set<Integer> enabledComplications =
+ getComplicationsEnabled()
+ ? new ArraySet<>(mSupportedComplications) : new ArraySet<>();
+
+ if (!getHomeControlsEnabled()) {
+ enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS);
+ } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) {
+ // Add home control type to list of enabled complications, even if other complications
+ // have been disabled.
+ enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS);
+ }
+ return enabledComplications;
}
/** Sets complication enabled state. */
@@ -290,6 +303,18 @@
Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
}
+ /** Sets whether home controls are enabled by the user on the dream */
+ public void setHomeControlsEnabled(boolean enabled) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
+ }
+
+ /** Gets whether home controls button is enabled on the dream */
+ private boolean getHomeControlsEnabled() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
+ }
+
/**
* Gets whether complications are enabled on this device
*/
@@ -304,6 +329,14 @@
return mSupportedComplications;
}
+ /**
+ * Sets the list of supported complications. Should only be used in tests.
+ */
+ @VisibleForTesting
+ public void setSupportedComplications(Set<Integer> complications) {
+ mSupportedComplications = complications;
+ }
+
public boolean isEnabled() {
return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 684a9aa..c9e8312 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -31,7 +31,6 @@
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
@@ -621,9 +620,11 @@
dispatchConnectedDeviceChanged(id);
}
+ /**
+ * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+ */
@Override
public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
- dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 6b9866b..071ab27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -33,7 +33,6 @@
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION_MANAGED;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
import static android.media.RouteListingPreference.Item.SUBTEXT_DEVICE_LOW_POWER;
@@ -45,6 +44,7 @@
import static android.media.RouteListingPreference.Item.SUBTEXT_UNAUTHORIZED;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -95,6 +95,17 @@
int TYPE_CAST_GROUP_DEVICE = 7;
}
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE,
+ SELECTION_BEHAVIOR_TRANSFER,
+ SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP
+ })
+ public @interface SelectionBehavior {
+ int SELECTION_BEHAVIOR_NONE = 0;
+ int SELECTION_BEHAVIOR_TRANSFER = 1;
+ int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+ }
+
@VisibleForTesting
int mType;
@@ -213,7 +224,7 @@
*
* @return selection behavior of device
*/
- @RouteListingPreference.Item.SubText
+ @SelectionBehavior
public int getSelectionBehavior() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_TRANSFER;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index f7fd25b..7ff0988 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -18,20 +18,35 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
+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.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.provider.Settings;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class ServiceListingTest {
@@ -39,10 +54,16 @@
private static final String TEST_INTENT = "com.example.intent";
private ServiceListing mServiceListing;
+ private Context mContext;
+ private PackageManager mPm;
@Before
public void setUp() {
- mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+ mPm = mock(PackageManager.class);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getPackageManager()).thenReturn(mPm);
+
+ mServiceListing = new ServiceListing.Builder(mContext)
.setTag("testTag")
.setSetting(TEST_SETTING)
.setNoun("testNoun")
@@ -52,6 +73,81 @@
}
@Test
+ public void testValidator() {
+ ServiceInfo s1 = new ServiceInfo();
+ s1.permission = "testPermission";
+ s1.packageName = "pkg";
+ ServiceInfo s2 = new ServiceInfo();
+ s2.permission = "testPermission";
+ s2.packageName = "pkg2";
+ ResolveInfo r1 = new ResolveInfo();
+ r1.serviceInfo = s1;
+ ResolveInfo r2 = new ResolveInfo();
+ r2.serviceInfo = s2;
+
+ when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+ ImmutableList.of(r1, r2));
+
+ mServiceListing = new ServiceListing.Builder(mContext)
+ .setTag("testTag")
+ .setSetting(TEST_SETTING)
+ .setNoun("testNoun")
+ .setIntentAction(TEST_INTENT)
+ .setValidator(info -> {
+ if (info.packageName.equals("pkg")) {
+ return true;
+ }
+ return false;
+ })
+ .setPermission("testPermission")
+ .build();
+ ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+ mServiceListing.addCallback(callback);
+ mServiceListing.reload();
+
+ verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+ ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+ verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+ assertThat(captor.getValue().size()).isEqualTo(1);
+ assertThat(captor.getValue().get(0)).isEqualTo(s1);
+ }
+
+ @Test
+ public void testNoValidator() {
+ ServiceInfo s1 = new ServiceInfo();
+ s1.permission = "testPermission";
+ s1.packageName = "pkg";
+ ServiceInfo s2 = new ServiceInfo();
+ s2.permission = "testPermission";
+ s2.packageName = "pkg2";
+ ResolveInfo r1 = new ResolveInfo();
+ r1.serviceInfo = s1;
+ ResolveInfo r2 = new ResolveInfo();
+ r2.serviceInfo = s2;
+
+ when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+ ImmutableList.of(r1, r2));
+
+ mServiceListing = new ServiceListing.Builder(mContext)
+ .setTag("testTag")
+ .setSetting(TEST_SETTING)
+ .setNoun("testNoun")
+ .setIntentAction(TEST_INTENT)
+ .setPermission("testPermission")
+ .build();
+ ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+ mServiceListing.addCallback(callback);
+ mServiceListing.reload();
+
+ verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+ ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+ verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+ assertThat(captor.getValue().size()).isEqualTo(2);
+ }
+
+ @Test
public void testCallback() {
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
mServiceListing.addCallback(callback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 52b9227..22ec12d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -16,6 +16,10 @@
package com.android.settingslib.dream;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_DATE;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_TIME;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -36,13 +40,16 @@
import org.robolectric.shadows.ShadowSettings;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowSettings.ShadowSecure.class})
public final class DreamBackendTest {
- private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3};
+ private static final int[] SUPPORTED_DREAM_COMPLICATIONS =
+ {COMPLICATION_TYPE_HOME_CONTROLS, COMPLICATION_TYPE_DATE,
+ COMPLICATION_TYPE_TIME};
private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream(
SUPPORTED_DREAM_COMPLICATIONS).boxed().collect(
Collectors.toList());
@@ -93,8 +100,52 @@
@Test
public void testDisableComplications() {
mBackend.setComplicationsEnabled(false);
- assertThat(mBackend.getEnabledComplications()).isEmpty();
+ assertThat(mBackend.getEnabledComplications())
+ .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS);
assertThat(mBackend.getComplicationsEnabled()).isFalse();
}
-}
+ @Test
+ public void testHomeControlsDisabled_ComplicationsEnabled() {
+ mBackend.setComplicationsEnabled(true);
+ mBackend.setHomeControlsEnabled(false);
+ // Home controls should not be enabled, only date and time.
+ final List<Integer> enabledComplications =
+ Arrays.asList(COMPLICATION_TYPE_DATE, COMPLICATION_TYPE_TIME);
+ assertThat(mBackend.getEnabledComplications())
+ .containsExactlyElementsIn(enabledComplications);
+ }
+
+ @Test
+ public void testHomeControlsDisabled_ComplicationsDisabled() {
+ mBackend.setComplicationsEnabled(false);
+ mBackend.setHomeControlsEnabled(false);
+ assertThat(mBackend.getEnabledComplications()).isEmpty();
+ }
+
+ @Test
+ public void testHomeControlsEnabled_ComplicationsDisabled() {
+ mBackend.setComplicationsEnabled(false);
+ mBackend.setHomeControlsEnabled(true);
+ // Home controls should not be enabled, only date and time.
+ final List<Integer> enabledComplications =
+ Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS);
+ assertThat(mBackend.getEnabledComplications())
+ .containsExactlyElementsIn(enabledComplications);
+ }
+
+ @Test
+ public void testHomeControlsEnabled_ComplicationsEnabled() {
+ mBackend.setComplicationsEnabled(true);
+ mBackend.setHomeControlsEnabled(true);
+ // Home controls should not be enabled, only date and time.
+ final List<Integer> enabledComplications =
+ Arrays.asList(
+ COMPLICATION_TYPE_HOME_CONTROLS,
+ COMPLICATION_TYPE_DATE,
+ COMPLICATION_TYPE_TIME
+ );
+ assertThat(mBackend.getEnabledComplications())
+ .containsExactlyElementsIn(enabledComplications);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f63c06a..270fda8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -30,6 +30,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -799,12 +800,12 @@
}
@Test
- public void onTransferFailed_shouldDispatchOnRequestFailed() {
+ public void onTransferFailed_notDispatchOnRequestFailed() {
mInfoMediaManager.registerCallback(mCallback);
mInfoMediaManager.mMediaRouterCallback.onTransferFailed(null, null);
- verify(mCallback).onRequestFailed(REASON_UNKNOWN_ERROR);
+ verify(mCallback, never()).onRequestFailed(REASON_UNKNOWN_ERROR);
}
@Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 09a1ba2..e50f522 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -49,6 +49,7 @@
Settings.Global.CHARGING_SOUNDS_ENABLED,
Settings.Global.USB_MASS_STORAGE_ENABLED,
Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ Settings.Global.NETWORK_AVOID_BAD_WIFI,
Settings.Global.WIFI_WAKEUP_ENABLED,
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
Settings.Global.USE_OPEN_WIFI_PACKAGE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f66fcba..3efb41d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -139,6 +139,7 @@
Settings.Secure.SCREENSAVER_COMPONENTS,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
Settings.Secure.VOLUME_HUSH_GESTURE,
Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e57cf3b..8d07fb6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,9 @@
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT;
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;
@@ -103,6 +106,14 @@
VALIDATORS.put(
Global.NETWORK_RECOMMENDATIONS_ENABLED,
new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+ VALIDATORS.put(
+ Global.NETWORK_AVOID_BAD_WIFI,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(NETWORK_AVOID_BAD_WIFI_IGNORE),
+ String.valueOf(NETWORK_AVOID_BAD_WIFI_PROMPT),
+ String.valueOf(NETWORK_AVOID_BAD_WIFI_AVOID),
+ }));
VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 558e19f..abd2c75 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -206,6 +206,7 @@
VALIDATORS.put(Secure.SCREENSAVER_COMPONENTS, COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR);
VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_DOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index db6cc1a..a8eeec3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.MemoryIntArray;
@@ -30,7 +31,7 @@
/**
* This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates shared memory regions which
+ * on a per-user basis and updates shared memory regions which
* client processes can read to determine if their local caches are
* stale.
*/
@@ -81,6 +82,10 @@
}
private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
+ if (SettingsState.isGlobalSettingsKey(key)) {
+ // Global settings are shared across users, so ignore the userId in the key
+ key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+ }
synchronized (mLock) {
final MemoryIntArray backingStore = getBackingStoreLocked(key,
/* createIfNotExist= */ false);
@@ -126,6 +131,10 @@
* returning the result.
*/
public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
+ if (SettingsState.isGlobalSettingsKey(key)) {
+ // Global settings are shared across users, so ignore the userId in the key
+ key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+ }
synchronized (mLock) {
final MemoryIntArray backingStore = getBackingStoreLocked(key,
/* createIfNotExist= */ true);
@@ -140,11 +149,9 @@
// Should not happen unless having error accessing the backing store
return;
}
- bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- backingStore);
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, backingStore);
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
- backingStore.get(index));
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index));
if (DEBUG) {
Slog.i(LOG_TAG, "Exported index:" + index + " for "
+ (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
@@ -189,7 +196,9 @@
if (backingStore == null) {
try {
if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
- Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+ }
return null;
}
backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
@@ -249,7 +258,9 @@
+ " on user:" + SettingsState.getUserIdFromKey(key));
}
} else {
- Slog.e(LOG_TAG, "Could not allocate generation index");
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Could not allocate generation index");
+ }
}
}
return index;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e6408bf..7607909 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,13 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.getTypeFromKey;
+import static com.android.providers.settings.SettingsState.getUserIdFromKey;
+import static com.android.providers.settings.SettingsState.isConfigSettingsKey;
+import static com.android.providers.settings.SettingsState.isGlobalSettingsKey;
+import static com.android.providers.settings.SettingsState.isSecureSettingsKey;
+import static com.android.providers.settings.SettingsState.isSsaidSettingsKey;
+import static com.android.providers.settings.SettingsState.isSystemSettingsKey;
import static com.android.providers.settings.SettingsState.makeKey;
import android.Manifest;
@@ -376,14 +383,6 @@
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
- public static int getTypeFromKey(int key) {
- return SettingsState.getTypeFromKey(key);
- }
-
- public static int getUserIdFromKey(int key) {
- return SettingsState.getUserIdFromKey(key);
- }
-
@ChangeId
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -3620,26 +3619,6 @@
}
}
- private boolean isConfigSettingsKey(int key) {
- return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
- }
-
- private boolean isGlobalSettingsKey(int key) {
- return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
- }
-
- private boolean isSystemSettingsKey(int key) {
- return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
- }
-
- private boolean isSecureSettingsKey(int key) {
- return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
- }
-
- private boolean isSsaidSettingsKey(int key) {
- return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
- }
-
private boolean shouldBan(int type) {
if (SETTINGS_TYPE_CONFIG != type) {
return false;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4d8705f..e3153e0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -255,6 +255,26 @@
}
}
+ public static boolean isConfigSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
+ }
+
+ public static boolean isGlobalSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
+ }
+
+ public static boolean isSystemSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
+ }
+
+ public static boolean isSecureSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
+ }
+
+ public static boolean isSsaidSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
+ }
+
public static String keyToString(int key) {
return "Key[user=" + getUserIdFromKey(key) + ";type="
+ settingTypeToString(getTypeFromKey(key)) + "]";
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index 9d23526..0bf53cc 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -14,6 +14,12 @@
limitations under the License.
-->
<configuration description="Run Settings Provider Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ <option name="force-skip-system-props" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsProviderTest.apk" />
</target_preparer>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 278ceb9..1f14723 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -372,7 +372,6 @@
Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
- Settings.Global.NETWORK_AVOID_BAD_WIFI,
Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 6ec8146..586d6f7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -170,6 +170,23 @@
checkBundle(b, 1, 2, false);
}
+ @Test
+ public void testGlobalSettings() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
+ final String testGlobalSetting = "test_global_setting";
+ final Bundle b = new Bundle();
+ generationRegistry.addGenerationData(b, globalKey, testGlobalSetting);
+ checkBundle(b, 0, 1, false);
+ final MemoryIntArray array = getArray(b);
+ final int globalKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 10);
+ b.clear();
+ generationRegistry.addGenerationData(b, globalKey2, testGlobalSetting);
+ checkBundle(b, 0, 1, false);
+ final MemoryIntArray array2 = getArray(b);
+ // Check that user10 and user0 use the same array to store global settings' generations
+ assertThat(array).isEqualTo(array2);
+ }
private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
throws IOException {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1136c11..4290ca0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -924,7 +924,7 @@
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+ android:configChanges="screenLayout|keyboard|keyboardHidden|orientation"
android:visibleToInstantApps="true">
</activity>
@@ -946,7 +946,7 @@
android:showWhenLocked="true"
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
- android:lockTaskMode="if_whitelisted"
+ android:lockTaskMode="always"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 8ca64d2..96ea5b4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -35,6 +35,7 @@
import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings;
+import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -56,6 +57,8 @@
public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
public static final String INTENT_HIDE_MENU = ".hide_menu";
+ public static final String INTENT_GLOBAL_ACTION = ".global_action";
+ public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -231,6 +234,22 @@
}
/**
+ * Performs global action and broadcasts an intent indicating the action was performed.
+ * This is unnecessary for any current functionality, but is used for testing.
+ * Refer to {@code performGlobalAction()}.
+ *
+ * @param globalAction Global action to be performed.
+ * @return {@code true} if successful, {@code false} otherwise.
+ */
+ private boolean performGlobalActionInternal(int globalAction) {
+ Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+ intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+ sendBroadcast(intent);
+ Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
+ return performGlobalAction(globalAction);
+ }
+
+ /**
* Handles click events of shortcuts.
*
* @param view the shortcut button being clicked.
@@ -249,17 +268,17 @@
new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
+ performGlobalActionInternal(GLOBAL_ACTION_POWER_DIALOG);
} else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_RECENTS);
+ performGlobalActionInternal(GLOBAL_ACTION_RECENTS);
} else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+ performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN);
} else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
+ performGlobalActionInternal(GLOBAL_ACTION_QUICK_SETTINGS);
} else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
+ performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS);
} else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
- performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
+ performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
} else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
return;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
index 7be6ca7..2be9245 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
@@ -29,4 +29,15 @@
android:targetPackage="com.android.systemui.accessibility.accessibilitymenu.tests"
android:label="AccessibilityMenu Test Cases">
</instrumentation>
+
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="https" />
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.VOICE_COMMAND" />
+ </intent>
+ </queries>
</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 529a70c..0e89dcd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -16,6 +16,15 @@
package com.android.systemui.accessibility.accessibilitymenu.tests;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_POWER_DIALOG;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
+
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
@@ -23,11 +32,15 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
import android.provider.Settings;
+import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -37,22 +50,29 @@
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
+import org.junit.After;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
public class AccessibilityMenuServiceTest {
private static final String TAG = "A11yMenuServiceTest";
+ private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
- private static final int TIMEOUT_UI_CHANGE_S = 5;
+ private static final int TIMEOUT_UI_CHANGE_S = 10;
+ private static final int NO_GLOBAL_ACTION = -1;
+ private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static AtomicInteger sLastGlobalAction;
private static AccessibilityManager sAccessibilityManager;
@@ -62,7 +82,7 @@
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
- final Context context = sInstrumentation.getContext();
+ final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
// Disable all a11yServices if any are active.
@@ -85,6 +105,17 @@
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
info -> info.getId().contains(serviceName)).count() == 1);
+
+ sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received global action intent.");
+ sLastGlobalAction.set(
+ intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
+ }},
+ new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+ null, null, Context.RECEIVER_EXPORTED);
}
@AfterClass
@@ -93,23 +124,39 @@
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
}
- private boolean isMenuVisible() {
- return sUiAutomation.getRootInActiveWindow() != null
- && sUiAutomation.getRootInActiveWindow().getPackageName().toString().equals(
- PACKAGE_NAME);
+ @Before
+ public void setup() throws Throwable {
+ openMenu();
}
- private void openMenu() throws Throwable {
- if (isMenuVisible()) {
- return;
- }
+ @After
+ public void tearDown() throws Throwable {
+ closeMenu();
+ sLastGlobalAction.set(NO_GLOBAL_ACTION);
+ }
+
+ private static boolean isMenuVisible() {
+ AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
+ return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
+ }
+
+ private static void openMenu() throws Throwable {
Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
sInstrumentation.getContext().sendBroadcast(intent);
+
TestUtils.waitUntil("Timed out before menu could appear.",
- TIMEOUT_UI_CHANGE_S, () -> isMenuVisible());
+ TIMEOUT_UI_CHANGE_S,
+ () -> {
+ if (isMenuVisible()) {
+ return true;
+ } else {
+ sInstrumentation.getContext().sendBroadcast(intent);
+ return false;
+ }
+ });
}
- private void closeMenu() throws Throwable {
+ private static void closeMenu() throws Throwable {
if (!isMenuVisible()) {
return;
}
@@ -119,11 +166,21 @@
TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
}
+ /**
+ * Provides list of all present shortcut buttons.
+ * @return List of shortcut buttons.
+ */
private List<AccessibilityNodeInfo> getGridButtonList() {
return sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(PACKAGE_NAME + ":id/shortcutIconBtn");
}
+ /**
+ * Returns the first button whose uniqueID matches the provided text.
+ * @param buttons List of buttons.
+ * @param text Text to match button's uniqueID to.
+ * @return Button whose uniqueID matches text, {@code null} otherwise.
+ */
private AccessibilityNodeInfo findGridButtonInfo(
List<AccessibilityNodeInfo> buttons, String text) {
for (AccessibilityNodeInfo button: buttons) {
@@ -136,8 +193,6 @@
@Test
public void testAdjustBrightness() throws Throwable {
- openMenu();
-
Context context = sInstrumentation.getTargetContext();
DisplayManager displayManager = context.getSystemService(
DisplayManager.class);
@@ -149,7 +204,6 @@
AccessibilityNodeInfo brightnessDownButton = findGridButtonInfo(buttons,
String.valueOf(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()));
- int clickId = AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.getId();
BrightnessInfo brightnessInfo = displayManager.getDisplay(
context.getDisplayId()).getBrightnessInfo();
@@ -159,7 +213,7 @@
TIMEOUT_UI_CHANGE_S,
() -> displayManager.getBrightness(context.getDisplayId())
== brightnessInfo.brightnessMinimum);
- brightnessUpButton.performAction(clickId);
+ brightnessUpButton.performAction(CLICK_ID);
TestUtils.waitUntil("Did not detect an increase in brightness.",
TIMEOUT_UI_CHANGE_S,
() -> displayManager.getBrightness(context.getDisplayId())
@@ -170,14 +224,155 @@
TIMEOUT_UI_CHANGE_S,
() -> displayManager.getBrightness(context.getDisplayId())
== brightnessInfo.brightnessMaximum);
- brightnessDownButton.performAction(clickId);
+ brightnessDownButton.performAction(CLICK_ID);
TestUtils.waitUntil("Did not detect a decrease in brightness.",
TIMEOUT_UI_CHANGE_S,
() -> displayManager.getBrightness(context.getDisplayId())
< brightnessInfo.brightnessMaximum);
} finally {
displayManager.setBrightness(context.getDisplayId(), resetBrightness);
- closeMenu();
}
}
+
+ @Test
+ public void testAdjustVolume() throws Throwable {
+ Context context = sInstrumentation.getTargetContext();
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ int resetVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+ List<AccessibilityNodeInfo> buttons = getGridButtonList();
+ AccessibilityNodeInfo volumeUpButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_VOLUME_UP_VALUE.ordinal()));
+ AccessibilityNodeInfo volumeDownButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()));
+
+ try {
+ int min = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, min,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ TestUtils.waitUntil("Could not change audio stream to minimum volume.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == min);
+ volumeUpButton.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect an increase in volume.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) > min);
+
+ int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ TestUtils.waitUntil("Could not change audio stream to maximum volume.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == max);
+ volumeDownButton.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect a decrease in volume.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) < max);
+ } finally {
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+ resetVolume, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ }
+ }
+
+ @Test
+ public void testAssistantButton_opensVoiceAssistant() throws Throwable {
+ AccessibilityNodeInfo assistantButton = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_ASSISTANT_VALUE.ordinal()));
+ Intent expectedIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ String expectedPackage = expectedIntent.resolveActivity(
+ sInstrumentation.getContext().getPackageManager()).getPackageName();
+
+ sUiAutomation.executeAndWaitForEvent(
+ () -> assistantButton.performAction(CLICK_ID),
+ (event) -> expectedPackage.contains(event.getPackageName()),
+ TIMEOUT_UI_CHANGE_S * 1000
+ );
+ }
+
+ @Test
+ public void testAccessibilitySettingsButton_opensAccessibilitySettings() throws Throwable {
+ AccessibilityNodeInfo settingsButton = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_A11YSETTING_VALUE.ordinal()));
+ Intent expectedIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+ String expectedPackage = expectedIntent.resolveActivity(
+ sInstrumentation.getContext().getPackageManager()).getPackageName();
+
+ sUiAutomation.executeAndWaitForEvent(
+ () -> settingsButton.performAction(CLICK_ID),
+ (event) -> expectedPackage.contains(event.getPackageName()),
+ TIMEOUT_UI_CHANGE_S * 1000
+ );
+ }
+
+ @Test
+ public void testPowerButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_POWER_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Power action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_POWER_DIALOG, NO_GLOBAL_ACTION));
+ }
+
+ @Test
+ public void testRecentButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_RECENT_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Recents action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_RECENTS, NO_GLOBAL_ACTION));
+ }
+
+ @Test
+ public void testLockButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Lock action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_LOCK_SCREEN, NO_GLOBAL_ACTION));
+ }
+
+ @Test
+ public void testQuickSettingsButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_QUICKSETTING_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Quick Settings action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_QUICK_SETTINGS, NO_GLOBAL_ACTION));
+ }
+
+ @Test
+ public void testNotificationsButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_NOTIFICATION_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Notifications action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_NOTIFICATIONS, NO_GLOBAL_ACTION));
+ }
+
+ @Test
+ public void testScreenshotButton_performsGlobalAction() throws Throwable {
+ AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+ String.valueOf(ShortcutId.ID_SCREENSHOT_VALUE.ordinal()));
+
+ button.performAction(CLICK_ID);
+ TestUtils.waitUntil("Did not detect Screenshot action being performed.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> sLastGlobalAction.compareAndSet(
+ GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION));
+ }
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
new file mode 100644
index 0000000..78ae4af
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -0,0 +1,59 @@
+package com.android.systemui.animation
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+class FontVariationUtils {
+ private var mWeight = -1
+ private var mWidth = -1
+ private var mOpticalSize = -1
+ private var mRoundness = -1
+ private var isUpdated = false
+
+ /*
+ * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator
+ * the order of axes should align to the order of parameters
+ * if every axis remains unchanged, return ""
+ */
+ fun updateFontVariation(
+ weight: Int = -1,
+ width: Int = -1,
+ opticalSize: Int = -1,
+ roundness: Int = -1
+ ): String {
+ isUpdated = false
+ if (weight >= 0 && mWeight != weight) {
+ isUpdated = true
+ mWeight = weight
+ }
+ if (width >= 0 && mWidth != width) {
+ isUpdated = true
+ mWidth = width
+ }
+ if (opticalSize >= 0 && mOpticalSize != opticalSize) {
+ isUpdated = true
+ mOpticalSize = opticalSize
+ }
+
+ if (roundness >= 0 && mRoundness != roundness) {
+ isUpdated = true
+ mRoundness = roundness
+ }
+ var resultString = ""
+ if (mWeight >= 0) {
+ resultString += "'$TAG_WGHT' $mWeight"
+ }
+ if (mWidth >= 0) {
+ resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth"
+ }
+ if (mOpticalSize >= 0) {
+ resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize"
+ }
+ if (mRoundness >= 0) {
+ resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness"
+ }
+ return if (isUpdated) resultString else ""
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 6946e6b..03e1e66 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -59,12 +59,14 @@
// changes should be ordered top-to-bottom in z
val mode = change.mode
+ var rootIdx = info.findRootIndex(change.endDisplayId)
+ if (rootIdx < 0) rootIdx = 0
// Launcher animates leaf tasks directly, so always reparent all task leashes to root.
- t.reparent(leash, info.rootLeash)
+ t.reparent(leash, info.getRoot(rootIdx).leash)
t.setPosition(
leash,
- (change.startAbsBounds.left - info.rootOffset.x).toFloat(),
- (change.startAbsBounds.top - info.rootOffset.y).toFloat()
+ (change.startAbsBounds.left - info.getRoot(rootIdx).offset.x).toFloat(),
+ (change.startAbsBounds.top - info.getRoot(rootIdx).offset.y).toFloat()
)
t.show(leash)
// Put all the OPEN/SHOW on top
@@ -114,8 +116,11 @@
.setName(change.leash.toString() + "_transition-leash")
.setContainerLayer()
.setParent(
- if (change.parent == null) info.rootLeash
- else info.getChange(change.parent!!)!!.leash
+ if (change.parent == null) {
+ var rootIdx = info.findRootIndex(change.endDisplayId)
+ if (rootIdx < 0) rootIdx = 0
+ info.getRoot(rootIdx).leash
+ } else info.getChange(change.parent!!)!!.leash
)
.build()
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 7fe94d3..9e9929e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,11 +23,8 @@
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
-import android.graphics.fonts.FontVariationAxis
import android.text.Layout
-import android.util.SparseArray
-private const val TAG_WGHT = "wght"
private const val DEFAULT_ANIMATION_DURATION: Long = 300
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -51,7 +48,7 @@
*
* // Change the text size with animation.
* fun setTextSize(sizePx: Float, animate: Boolean) {
- * animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
+ * animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate)
* }
* }
* ```
@@ -115,7 +112,9 @@
protected set
}
- private val typefaceCache = SparseArray<Typeface?>()
+ private val fontVariationUtils = FontVariationUtils()
+
+ private val typefaceCache = HashMap<String, Typeface?>()
fun updateLayout(layout: Layout) {
textInterpolator.layout = layout
@@ -186,7 +185,7 @@
* Bu passing -1 to duration, the default text animation, 1000ms, is used.
* By passing false to animate, the text will be updated without animation.
*
- * @param weight an optional text weight.
+ * @param fvar an optional text fontVariationSettings.
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator
@@ -199,7 +198,7 @@
* will be used. This is ignored if animate is false.
*/
fun setTextStyle(
- weight: Int = -1,
+ fvar: String? = "",
textSize: Float = -1f,
color: Int? = null,
strokeWidth: Float = -1f,
@@ -217,42 +216,16 @@
if (textSize >= 0) {
textInterpolator.targetPaint.textSize = textSize
}
- if (weight >= 0) {
- val fontVariationArray =
- FontVariationAxis.fromFontVariationSettings(
- textInterpolator.targetPaint.fontVariationSettings
- )
- if (fontVariationArray.isNullOrEmpty()) {
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface
- }
- } else {
- val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
- if (idx == -1) {
- val updatedFontVariation =
- textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings =
- updatedFontVariation
- textInterpolator.targetPaint.typeface
- }
- } else {
- fontVariationArray[idx] = FontVariationAxis(
- "$TAG_WGHT", weight.toFloat())
- val updatedFontVariation =
- FontVariationAxis.toFontVariationSettings(fontVariationArray)
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings =
- updatedFontVariation
- textInterpolator.targetPaint.typeface
- }
+
+ if (!fvar.isNullOrBlank()) {
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(fvar) {
+ textInterpolator.targetPaint.fontVariationSettings = fvar
+ typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
+ textInterpolator.targetPaint.typeface
}
- }
}
+
if (color != null) {
textInterpolator.targetPaint.color = color
}
@@ -291,13 +264,56 @@
invalidateCallback()
}
}
+
+ /**
+ * Set text style with animation. Similar as
+ * fun setTextStyle(
+ * fvar: String? = "",
+ * textSize: Float = -1f,
+ * color: Int? = null,
+ * strokeWidth: Float = -1f,
+ * animate: Boolean = true,
+ * duration: Long = -1L,
+ * interpolator: TimeInterpolator? = null,
+ * delay: Long = 0,
+ * onAnimationEnd: Runnable? = null
+ * )
+ *
+ * @param weight an optional style value for `wght` in fontVariationSettings.
+ * @param width an optional style value for `wdth` in fontVariationSettings.
+ * @param opticalSize an optional style value for `opsz` in fontVariationSettings.
+ * @param roundness an optional style value for `ROND` in fontVariationSettings.
+ */
+ fun setTextStyle(
+ weight: Int = -1,
+ width: Int = -1,
+ opticalSize: Int = -1,
+ roundness: Int = -1,
+ textSize: Float = -1f,
+ color: Int? = null,
+ strokeWidth: Float = -1f,
+ animate: Boolean = true,
+ duration: Long = -1L,
+ interpolator: TimeInterpolator? = null,
+ delay: Long = 0,
+ onAnimationEnd: Runnable? = null
+ ) {
+ val fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,)
+ setTextStyle(
+ fvar = fvar,
+ textSize = textSize,
+ color = color,
+ strokeWidth = strokeWidth,
+ animate = animate,
+ duration = duration,
+ interpolator = interpolator,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd,
+ )
+ }
}
-private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
- var v = get(key)
- if (v == null) {
- v = defaultValue()
- put(key, v)
- }
- return v
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index d78e0c1..280e7ed9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -53,6 +53,19 @@
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
}
+ // Perceived luminosity (L′), not absolute luminosity.
+ half getLuminosity(vec3 c) {
+ return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
+ }
+
+ // Creates a luminosity mask and clamp to the legal range.
+ vec3 maskLuminosity(vec3 dest, float lum) {
+ dest.rgb *= vec3(lum);
+ // Clip back into the legal range
+ dest = clamp(dest, vec3(0.), vec3(1.0));
+ return dest;
+ }
+
// Return range [-1, 1].
vec3 hash(vec3 p) {
p = fract(p * vec3(.3456, .1234, .9876));
@@ -62,14 +75,14 @@
}
// Skew factors (non-uniform).
- const float SKEW = 0.3333333; // 1/3
- const float UNSKEW = 0.1666667; // 1/6
+ const half SKEW = 0.3333333; // 1/3
+ const half UNSKEW = 0.1666667; // 1/6
// Return range roughly [-1,1].
// It's because the hash function (that returns a random gradient vector) returns
// different magnitude of vectors. Noise doesn't have to be in the precise range thus
// skipped normalize.
- float simplex3d(vec3 p) {
+ half simplex3d(vec3 p) {
// Skew the input coordinate, so that we get squashed cubical grid
vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
@@ -143,6 +156,22 @@
// Should multiply by the possible max contribution to adjust the range in [-1,1].
return dot(vec4(32.), nc);
}
+
+ // Random rotations.
+ // The way you create fractal noise is layering simplex noise with some rotation.
+ // To make random cloud looking noise, the rotations should not align. (Otherwise it
+ // creates patterned noise).
+ // Below rotations only rotate in one axis.
+ const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15);
+ const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95);
+ const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44);
+
+ // Octave = 4
+ // Divide each coefficient by 3 to produce more grainy noise.
+ half simplex3d_fractal(vec3 mat) {
+ return 0.675 * simplex3d(mat * rot1) + 0.225 * simplex3d(2.0 * mat * rot2)
+ + 0.075 * simplex3d(4.0 * mat * rot3) + 0.025 * simplex3d(8.0 * mat);
+ }
"""
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 7456c43..0e22667 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -19,8 +19,13 @@
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import java.lang.Float.max
-/** Shader that renders turbulence simplex noise, with no octave. */
-class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+/**
+ * Shader that renders turbulence simplex noise, by default no octave.
+ *
+ * @param useFractal whether to use fractal noise (4 octaves).
+ */
+class TurbulenceNoiseShader(useFractal: Boolean = false) :
+ RuntimeShader(if (useFractal) FRACTAL_NOISE_SHADER else SIMPLEX_NOISE_SHADER) {
// language=AGSL
companion object {
private const val UNIFORMS =
@@ -35,21 +40,7 @@
layout(color) uniform vec4 in_backgroundColor;
"""
- private const val SHADER_LIB =
- """
- float getLuminosity(vec3 c) {
- return 0.3*c.r + 0.59*c.g + 0.11*c.b;
- }
-
- vec3 maskLuminosity(vec3 dest, float lum) {
- dest.rgb *= vec3(lum);
- // Clip back into the legal range
- dest = clamp(dest, vec3(0.), vec3(1.0));
- return dest;
- }
- """
-
- private const val MAIN_SHADER =
+ private const val SIMPLEX_SHADER =
"""
vec4 main(vec2 p) {
vec2 uv = p / in_size.xy;
@@ -71,8 +62,26 @@
}
"""
- private const val TURBULENCE_NOISE_SHADER =
- ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+ private const val FRACTAL_SHADER =
+ """
+ vec4 main(vec2 p) {
+ vec2 uv = p / in_size.xy;
+ uv.x *= in_aspectRatio;
+
+ vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+ float luma = simplex3d_fractal(noiseP) * in_opacity;
+ vec3 mask = maskLuminosity(in_color.rgb, luma);
+ vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+ // Skip dithering.
+ return vec4(color * in_color.a, in_color.a);
+ }
+ """
+
+ private const val SIMPLEX_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
+ private const val FRACTAL_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
}
/** Sets the number of grid for generating noise. */
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index 459a38e..09762b0 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,10 +25,12 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.EnumSet
import java.util.regex.Pattern
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
+@Suppress("UnstableApiUsage") // For linter api
class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes(): List<Class<out UElement>> {
return listOf(UAnnotation::class.java)
@@ -39,18 +41,15 @@
override fun visitAnnotation(node: UAnnotation) {
// Annotations having int bugId field
if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
- val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
- if (bugId <= 0) {
+ if (!containsBugId(node)) {
val location = context.getLocation(node)
val message = "Please attach a bug id to track demoted test"
context.report(ISSUE, node, location, message)
}
}
- // @Ignore has a String field for reason
+ // @Ignore has a String field for specifying reasons
if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
- val reason = node.findAttributeValue("value")!!.evaluate() as String
- val bugPattern = Pattern.compile("b/\\d+")
- if (!bugPattern.matcher(reason).find()) {
+ if (!containsBugString(node)) {
val location = context.getLocation(node)
val message = "Please attach a bug (e.g. b/123) to track demoted test"
context.report(ISSUE, node, location, message)
@@ -60,6 +59,17 @@
}
}
+ private fun containsBugId(node: UAnnotation): Boolean {
+ val bugId = node.findAttributeValue("bugId")?.evaluate() as Int?
+ return bugId != null && bugId > 0
+ }
+
+ private fun containsBugString(node: UAnnotation): Boolean {
+ val reason = node.findAttributeValue("value")?.evaluate() as String?
+ val bugPattern = Pattern.compile("b/\\d+")
+ return reason != null && bugPattern.matcher(reason).find()
+ }
+
companion object {
val DEMOTING_ANNOTATION_BUG_ID =
listOf(
@@ -87,7 +97,7 @@
implementation =
Implementation(
DemotingTestWithoutBugDetector::class.java,
- Scope.JAVA_FILE_SCOPE
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 63eb263..a1e6f92 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -20,8 +20,11 @@
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import java.util.EnumSet
import org.junit.Test
+@Suppress("UnstableApiUsage")
class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
@@ -45,6 +48,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expectClean()
@@ -65,6 +69,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expectClean()
@@ -88,6 +93,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expect(
@@ -115,6 +121,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expect(
@@ -145,6 +152,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expectClean()
@@ -168,6 +176,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expect(
@@ -198,6 +207,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expectClean()
@@ -221,6 +231,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expect(
@@ -248,6 +259,7 @@
.indented(),
*stubs
)
+ .customScope(testScope)
.issues(DemotingTestWithoutBugDetector.ISSUE)
.run()
.expect(
@@ -260,6 +272,7 @@
)
}
+ private val testScope = EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
private val filtersFlakyTestStub: TestFile =
java(
"""
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
new file mode 100644
index 0000000..946e779
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.compose.swipeable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
+import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
+import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
+import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
+import com.android.compose.ui.util.lerp
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods to
+ * change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ *
+ * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
+ */
+@Stable
+open class SwipeableState<T>(
+ initialValue: T,
+ internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+ internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+ /**
+ * The current value of the state.
+ *
+ * If no swipe or animation is in progress, this corresponds to the anchor at which the
+ * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+ * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+ */
+ var currentValue: T by mutableStateOf(initialValue)
+ private set
+
+ /** Whether the state is currently animating. */
+ var isAnimationRunning: Boolean by mutableStateOf(false)
+ private set
+
+ /**
+ * The current position (in pixels) of the [swipeable].
+ *
+ * You should use this state to offset your content accordingly. The recommended way is to use
+ * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+ */
+ val offset: State<Float>
+ get() = offsetState
+
+ /** The amount by which the [swipeable] has been swiped past its bounds. */
+ val overflow: State<Float>
+ get() = overflowState
+
+ // Use `Float.NaN` as a placeholder while the state is uninitialised.
+ private val offsetState = mutableStateOf(0f)
+ private val overflowState = mutableStateOf(0f)
+
+ // the source of truth for the "real"(non ui) position
+ // basically position in bounds + overflow
+ private val absoluteOffset = mutableStateOf(0f)
+
+ // current animation target, if animating, otherwise null
+ private val animationTarget = mutableStateOf<Float?>(null)
+
+ internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+ private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+ snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
+
+ internal var minBound = Float.NEGATIVE_INFINITY
+ internal var maxBound = Float.POSITIVE_INFINITY
+
+ internal fun ensureInit(newAnchors: Map<Float, T>) {
+ if (anchors.isEmpty()) {
+ // need to do initial synchronization synchronously :(
+ val initialOffset = newAnchors.getOffset(currentValue)
+ requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+ offsetState.value = initialOffset
+ absoluteOffset.value = initialOffset
+ }
+ }
+
+ internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
+ if (oldAnchors.isEmpty()) {
+ // If this is the first time that we receive anchors, then we need to initialise
+ // the state so we snap to the offset associated to the initial value.
+ minBound = newAnchors.keys.minOrNull()!!
+ maxBound = newAnchors.keys.maxOrNull()!!
+ val initialOffset = newAnchors.getOffset(currentValue)
+ requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+ snapInternalToOffset(initialOffset)
+ } else if (newAnchors != oldAnchors) {
+ // If we have received new anchors, then the offset of the current value might
+ // have changed, so we need to animate to the new offset. If the current value
+ // has been removed from the anchors then we animate to the closest anchor
+ // instead. Note that this stops any ongoing animation.
+ minBound = Float.NEGATIVE_INFINITY
+ maxBound = Float.POSITIVE_INFINITY
+ val animationTargetValue = animationTarget.value
+ // if we're in the animation already, let's find it a new home
+ val targetOffset =
+ if (animationTargetValue != null) {
+ // first, try to map old state to the new state
+ val oldState = oldAnchors[animationTargetValue]
+ val newState = newAnchors.getOffset(oldState)
+ // return new state if exists, or find the closes one among new anchors
+ newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+ } else {
+ // we're not animating, proceed by finding the new anchors for an old value
+ val actualOldValue = oldAnchors[offset.value]
+ val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+ newAnchors.getOffset(value)
+ ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
+ }
+ try {
+ animateInternalToOffset(targetOffset, animationSpec)
+ } catch (c: CancellationException) {
+ // If the animation was interrupted for any reason, snap as a last resort.
+ snapInternalToOffset(targetOffset)
+ } finally {
+ currentValue = newAnchors.getValue(targetOffset)
+ minBound = newAnchors.keys.minOrNull()!!
+ maxBound = newAnchors.keys.maxOrNull()!!
+ }
+ }
+ }
+
+ internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+ internal var velocityThreshold by mutableStateOf(0f)
+
+ internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+ internal val draggableState = DraggableState {
+ val newAbsolute = absoluteOffset.value + it
+ val clamped = newAbsolute.coerceIn(minBound, maxBound)
+ val overflow = newAbsolute - clamped
+ val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+ offsetState.value = clamped + resistanceDelta
+ overflowState.value = overflow
+ absoluteOffset.value = newAbsolute
+ }
+
+ private suspend fun snapInternalToOffset(target: Float) {
+ draggableState.drag { dragBy(target - absoluteOffset.value) }
+ }
+
+ private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+ draggableState.drag {
+ var prevValue = absoluteOffset.value
+ animationTarget.value = target
+ isAnimationRunning = true
+ try {
+ Animatable(prevValue).animateTo(target, spec) {
+ dragBy(this.value - prevValue)
+ prevValue = this.value
+ }
+ } finally {
+ animationTarget.value = null
+ isAnimationRunning = false
+ }
+ }
+ }
+
+ /**
+ * The target value of the state.
+ *
+ * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+ * swipe finished. If an animation is running, this is the target value of that animation.
+ * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+ */
+ val targetValue: T
+ get() {
+ // TODO(calintat): Track current velocity (b/149549482) and use that here.
+ val target =
+ animationTarget.value
+ ?: computeTarget(
+ offset = offset.value,
+ lastValue = anchors.getOffset(currentValue) ?: offset.value,
+ anchors = anchors.keys,
+ thresholds = thresholds,
+ velocity = 0f,
+ velocityThreshold = Float.POSITIVE_INFINITY
+ )
+ return anchors[target] ?: currentValue
+ }
+
+ /**
+ * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+ *
+ * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+ */
+ val progress: SwipeProgress<T>
+ get() {
+ val bounds = findBounds(offset.value, anchors.keys)
+ val from: T
+ val to: T
+ val fraction: Float
+ when (bounds.size) {
+ 0 -> {
+ from = currentValue
+ to = currentValue
+ fraction = 1f
+ }
+ 1 -> {
+ from = anchors.getValue(bounds[0])
+ to = anchors.getValue(bounds[0])
+ fraction = 1f
+ }
+ else -> {
+ val (a, b) =
+ if (direction > 0f) {
+ bounds[0] to bounds[1]
+ } else {
+ bounds[1] to bounds[0]
+ }
+ from = anchors.getValue(a)
+ to = anchors.getValue(b)
+ fraction = (offset.value - a) / (b - a)
+ }
+ }
+ return SwipeProgress(from, to, fraction)
+ }
+
+ /**
+ * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+ *
+ * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+ * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+ */
+ val direction: Float
+ get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+ /**
+ * Set the state without any animation and suspend until it's set
+ *
+ * @param targetValue The new target value to set [currentValue] to.
+ */
+ suspend fun snapTo(targetValue: T) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ val targetOffset = anchors.getOffset(targetValue)
+ requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+ snapInternalToOffset(targetOffset)
+ currentValue = targetValue
+ }
+ }
+
+ /**
+ * Set the state to the target value by starting an animation.
+ *
+ * @param targetValue The new value to animate to.
+ * @param anim The animation that will be used to animate to the new value.
+ */
+ suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ try {
+ val targetOffset = anchors.getOffset(targetValue)
+ requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+ animateInternalToOffset(targetOffset, anim)
+ } finally {
+ val endOffset = absoluteOffset.value
+ val endValue =
+ anchors
+ // fighting rounding error once again, anchor should be as close as 0.5
+ // pixels
+ .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+ .values
+ .firstOrNull()
+ ?: currentValue
+ currentValue = endValue
+ }
+ }
+ }
+
+ /**
+ * Perform fling with settling to one of the anchors which is determined by the given
+ * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+ * since it will settle at the anchor.
+ *
+ * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
+ * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+ * trigger settling fling when the child scroll container reaches the bound.
+ *
+ * @param velocity velocity to fling and settle with
+ * @return the reason fling ended
+ */
+ suspend fun performFling(velocity: Float) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ val lastAnchor = anchors.getOffset(currentValue)!!
+ val targetValue =
+ computeTarget(
+ offset = offset.value,
+ lastValue = lastAnchor,
+ anchors = anchors.keys,
+ thresholds = thresholds,
+ velocity = velocity,
+ velocityThreshold = velocityThreshold
+ )
+ val targetState = anchors[targetValue]
+ if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+ // If the user vetoed the state change, rollback to the previous state.
+ else animateInternalToOffset(lastAnchor, animationSpec)
+ }
+ }
+
+ /**
+ * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+ * gesture flow.
+ *
+ * Note: This method performs generic drag and it won't settle to any particular anchor, *
+ * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
+ * well to ensure swipeable will settle at the anchor.
+ *
+ * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
+ * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+ * force drag when the child scroll container reaches the bound.
+ *
+ * @param delta delta in pixels to drag by
+ * @return the amount of [delta] consumed
+ */
+ fun performDrag(delta: Float): Float {
+ val potentiallyConsumed = absoluteOffset.value + delta
+ val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+ val deltaToConsume = clamped - absoluteOffset.value
+ if (abs(deltaToConsume) > 0) {
+ draggableState.dispatchRawDelta(deltaToConsume)
+ }
+ return deltaToConsume
+ }
+
+ companion object {
+ /** The default [Saver] implementation for [SwipeableState]. */
+ fun <T : Any> Saver(
+ animationSpec: AnimationSpec<Float>,
+ confirmStateChange: (T) -> Boolean
+ ) =
+ Saver<SwipeableState<T>, T>(
+ save = { it.currentValue },
+ restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+ )
+ }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to]. Must
+ * be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+ val from: T,
+ val to: T,
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ val fraction: Float
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SwipeProgress<*>) return false
+
+ if (from != other.from) return false
+ if (to != other.to) return false
+ if (fraction != other.fraction) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = from?.hashCode() ?: 0
+ result = 31 * result + (to?.hashCode() ?: 0)
+ result = 31 * result + fraction.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+ }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+ initialValue: T,
+ animationSpec: AnimationSpec<Float> = AnimationSpec,
+ confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+ return rememberSaveable(
+ saver =
+ SwipeableState.Saver(
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+ )
+ ) {
+ SwipeableState(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+ )
+ }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ * [value] will be notified to update their state to the new value of the [SwipeableState] by
+ * invoking [onValueChange]. If the owner does not update their state to the provided value for
+ * some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+ value: T,
+ onValueChange: (T) -> Unit,
+ animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+ val swipeableState = remember {
+ SwipeableState(
+ initialValue = value,
+ animationSpec = animationSpec,
+ confirmStateChange = { true }
+ )
+ }
+ val forceAnimationCheck = remember { mutableStateOf(false) }
+ LaunchedEffect(value, forceAnimationCheck.value) {
+ if (value != swipeableState.currentValue) {
+ swipeableState.animateTo(value)
+ }
+ }
+ DisposableEffect(swipeableState.currentValue) {
+ if (value != swipeableState.currentValue) {
+ onValueChange(swipeableState.currentValue)
+ forceAnimationCheck.value = !forceAnimationCheck.value
+ }
+ onDispose {}
+ }
+ return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
+ * this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
+ * new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ * used to determine which state to animate to when swiping stops. This is represented as a lambda
+ * that takes two states and returns the threshold between them in the form of a
+ * [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
+ * will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
+ * [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
+ * order to animate to the next state, even if the positional [thresholds] have not been reached.
+ * @sample androidx.compose.material.samples.SwipeableSample
+ */
+fun <T> Modifier.swipeable(
+ state: SwipeableState<T>,
+ anchors: Map<Float, T>,
+ orientation: Orientation,
+ enabled: Boolean = true,
+ reverseDirection: Boolean = false,
+ interactionSource: MutableInteractionSource? = null,
+ thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+ resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+ velocityThreshold: Dp = VelocityThreshold
+) =
+ composed(
+ inspectorInfo =
+ debugInspectorInfo {
+ name = "swipeable"
+ properties["state"] = state
+ properties["anchors"] = anchors
+ properties["orientation"] = orientation
+ properties["enabled"] = enabled
+ properties["reverseDirection"] = reverseDirection
+ properties["interactionSource"] = interactionSource
+ properties["thresholds"] = thresholds
+ properties["resistance"] = resistance
+ properties["velocityThreshold"] = velocityThreshold
+ }
+ ) {
+ require(anchors.isNotEmpty()) { "You must have at least one anchor." }
+ require(anchors.values.distinct().count() == anchors.size) {
+ "You cannot have two anchors mapped to the same state."
+ }
+ val density = LocalDensity.current
+ state.ensureInit(anchors)
+ LaunchedEffect(anchors, state) {
+ val oldAnchors = state.anchors
+ state.anchors = anchors
+ state.resistance = resistance
+ state.thresholds = { a, b ->
+ val from = anchors.getValue(a)
+ val to = anchors.getValue(b)
+ with(thresholds(from, to)) { density.computeThreshold(a, b) }
+ }
+ with(density) { state.velocityThreshold = velocityThreshold.toPx() }
+ state.processNewAnchors(oldAnchors, anchors)
+ }
+
+ Modifier.draggable(
+ orientation = orientation,
+ enabled = enabled,
+ reverseDirection = reverseDirection,
+ interactionSource = interactionSource,
+ startDragImmediately = state.isAnimationRunning,
+ onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+ state = state.draggableState
+ )
+ }
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+ /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
+ fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+ override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+ return fromValue + offset.toPx() * sign(toValue - fromValue)
+ }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ private val fraction: Float
+) : ThresholdConfig {
+ override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+ return lerp(fromValue, toValue, fraction)
+ }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
+ * amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied to,
+ * or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
+ * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
+ * right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
+ * negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
+ * negative.
+ */
+@Immutable
+class ResistanceConfig(
+ /*@FloatRange(from = 0.0, fromInclusive = false)*/
+ val basis: Float,
+ /*@FloatRange(from = 0.0)*/
+ val factorAtMin: Float = StandardResistanceFactor,
+ /*@FloatRange(from = 0.0)*/
+ val factorAtMax: Float = StandardResistanceFactor
+) {
+ fun computeResistance(overflow: Float): Float {
+ val factor = if (overflow < 0) factorAtMin else factorAtMax
+ if (factor == 0f) return 0f
+ val progress = (overflow / basis).coerceIn(-1f, 1f)
+ return basis / factor * sin(progress * PI.toFloat() / 2)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ResistanceConfig) return false
+
+ if (basis != other.basis) return false
+ if (factorAtMin != other.factorAtMin) return false
+ if (factorAtMax != other.factorAtMax) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = basis.hashCode()
+ result = 31 * result + factorAtMin.hashCode()
+ result = 31 * result + factorAtMax.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+ }
+}
+
+/**
+ * Given an offset x and a set of anchors, return a list of anchors:
+ * 1. [ ] if the set of anchors is empty,
+ * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
+ * x rounded to the exact value of the matching anchor,
+ * 3. [ min ] if min is the minimum anchor and x < min,
+ * 4. [ max ] if max is the maximum anchor and x > max, or
+ * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
+ // Find the anchors the target lies between with a little bit of rounding error.
+ val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+ val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+ return when {
+ a == null ->
+ // case 1 or 3
+ listOfNotNull(b)
+ b == null ->
+ // case 4
+ listOf(a)
+ a == b ->
+ // case 2
+ // Can't return offset itself here since it might not be exactly equal
+ // to the anchor, despite being considered an exact match.
+ listOf(a)
+ else ->
+ // case 5
+ listOf(a, b)
+ }
+}
+
+private fun computeTarget(
+ offset: Float,
+ lastValue: Float,
+ anchors: Set<Float>,
+ thresholds: (Float, Float) -> Float,
+ velocity: Float,
+ velocityThreshold: Float
+): Float {
+ val bounds = findBounds(offset, anchors)
+ return when (bounds.size) {
+ 0 -> lastValue
+ 1 -> bounds[0]
+ else -> {
+ val lower = bounds[0]
+ val upper = bounds[1]
+ if (lastValue <= offset) {
+ // Swiping from lower to upper (positive).
+ if (velocity >= velocityThreshold) {
+ return upper
+ } else {
+ val threshold = thresholds(lower, upper)
+ if (offset < threshold) lower else upper
+ }
+ } else {
+ // Swiping from upper to lower (negative).
+ if (velocity <= -velocityThreshold) {
+ return lower
+ } else {
+ val threshold = thresholds(upper, lower)
+ if (offset > threshold) upper else lower
+ }
+ }
+ }
+ }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+ return entries.firstOrNull { it.value == state }?.key
+}
+
+/** Contains useful defaults for [swipeable] and [SwipeableState]. */
+object SwipeableDefaults {
+ /** The default animation used by [SwipeableState]. */
+ val AnimationSpec = SpringSpec<Float>()
+
+ /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
+ val VelocityThreshold = 125.dp
+
+ /** A stiff resistance factor which indicates that swiping isn't available right now. */
+ const val StiffResistanceFactor = 20f
+
+ /** A standard resistance factor which indicates that the user has run out of things to see. */
+ const val StandardResistanceFactor = 10f
+
+ /**
+ * The default resistance config used by [swipeable].
+ *
+ * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
+ * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+ */
+ fun resistanceConfig(
+ anchors: Set<Float>,
+ factorAtMin: Float = StandardResistanceFactor,
+ factorAtMax: Float = StandardResistanceFactor
+ ): ResistanceConfig? {
+ return if (anchors.size <= 1) {
+ null
+ } else {
+ val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+ ResistanceConfig(basis, factorAtMin, factorAtMax)
+ }
+ }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+ get() =
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val delta = available.toFloat()
+ return if (delta < 0 && source == NestedScrollSource.Drag) {
+ performDrag(delta).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ return if (source == NestedScrollSource.Drag) {
+ performDrag(available.toFloat()).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ val toFling = Offset(available.x, available.y).toFloat()
+ return if (toFling < 0 && offset.value > minBound) {
+ performFling(velocity = toFling)
+ // since we go to the anchor with tween settling, consume all for the best UX
+ available
+ } else {
+ Velocity.Zero
+ }
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ performFling(velocity = Offset(available.x, available.y).toFloat())
+ return available
+ }
+
+ private fun Float.toOffset(): Offset = Offset(0f, this)
+
+ private fun Offset.toFloat(): Float = this.y
+ }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
new file mode 100644
index 0000000..c1defb7
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.compose.ui.util
+
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Float, stop: Float, fraction: Float): Float {
+ return (1 - fraction) * start + fraction * stop
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Int, stop: Int, fraction: Float): Int {
+ return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+ return start + ((stop - start) * fraction.toDouble()).roundToLong()
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index e253fb9..cc33745 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,8 +21,10 @@
import android.view.View
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
/** The Compose facade, when Compose is *not* available. */
object ComposeFacade : BaseComposeFacade {
@@ -48,6 +50,14 @@
throwComposeUnavailableError()
}
+ override fun createMultiShadeView(
+ context: Context,
+ viewModel: MultiShadeViewModel,
+ clock: SystemClock,
+ ): View {
+ throwComposeUnavailableError()
+ }
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1ea18fe..0e79b18 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,10 +23,13 @@
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
+import com.android.systemui.multishade.ui.composable.MultiShade
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
@@ -51,4 +54,21 @@
setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
}
}
+
+ override fun createMultiShadeView(
+ context: Context,
+ viewModel: MultiShadeViewModel,
+ clock: SystemClock,
+ ): View {
+ return ComposeView(context).apply {
+ setContent {
+ PlatformTheme {
+ MultiShade(
+ viewModel = viewModel,
+ clock = clock,
+ )
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index 772c891..fbd7f83 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -49,14 +49,12 @@
// initializer are created once, when the process is started.
val savedStateRegistryOwner =
object : SavedStateRegistryOwner {
- private val savedStateRegistry =
+ private val savedStateRegistryController =
SavedStateRegistryController.create(this).apply { performRestore(null) }
- override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+ override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
- override fun getSavedStateRegistry(): SavedStateRegistry {
- return savedStateRegistry.savedStateRegistry
- }
+ override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
}
// We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
new file mode 100644
index 0000000..b9e38cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.IntSize
+import com.android.systemui.R
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.statusbar.ui.composable.StatusBar
+import com.android.systemui.util.time.SystemClock
+
+@Composable
+fun MultiShade(
+ viewModel: MultiShadeViewModel,
+ clock: SystemClock,
+ modifier: Modifier = Modifier,
+) {
+ val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
+
+ // TODO(b/273298030): find a different way to get the height constraint from its parent.
+ BoxWithConstraints(modifier = modifier) {
+ val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
+
+ Scrim(
+ modifier = Modifier.fillMaxSize(),
+ remoteTouch = viewModel::onScrimTouched,
+ alpha = { viewModel.scrimAlpha.value },
+ isScrimEnabled = isScrimEnabled,
+ )
+ Shade(
+ viewModel = viewModel.leftShade,
+ currentTimeMillis = clock::elapsedRealtime,
+ containerHeightPx = maxHeightPx,
+ modifier = Modifier.align(Alignment.TopStart),
+ ) {
+ Column {
+ StatusBar()
+ Notifications()
+ }
+ }
+ Shade(
+ viewModel = viewModel.rightShade,
+ currentTimeMillis = clock::elapsedRealtime,
+ containerHeightPx = maxHeightPx,
+ modifier = Modifier.align(Alignment.TopEnd),
+ ) {
+ Column {
+ StatusBar()
+ QuickSettings()
+ }
+ }
+ Shade(
+ viewModel = viewModel.singleShade,
+ currentTimeMillis = clock::elapsedRealtime,
+ containerHeightPx = maxHeightPx,
+ modifier = Modifier,
+ ) {
+ Column {
+ StatusBar()
+ Notifications()
+ QuickSettings()
+ }
+ }
+ }
+}
+
+@Composable
+private fun Scrim(
+ remoteTouch: (ProxiedInputModel) -> Unit,
+ alpha: () -> Float,
+ isScrimEnabled: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ var size by remember { mutableStateOf(IntSize.Zero) }
+
+ Box(
+ modifier =
+ modifier
+ .graphicsLayer { this.alpha = alpha() }
+ .background(colorResource(R.color.opaque_scrim))
+ .fillMaxSize()
+ .onSizeChanged { size = it }
+ .then(
+ if (isScrimEnabled) {
+ Modifier.pointerInput(Unit) {
+ detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
+ }
+ .pointerInput(Unit) {
+ detectVerticalDragGestures(
+ onVerticalDrag = { change, dragAmount ->
+ remoteTouch(
+ ProxiedInputModel.OnDrag(
+ xFraction = change.position.x / size.width,
+ yDragAmountPx = dragAmount,
+ )
+ )
+ },
+ onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
+ onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
+ )
+ }
+ } else {
+ Modifier
+ }
+ )
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
new file mode 100644
index 0000000..98ef57f9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.height
+import com.android.compose.swipeable.FixedThreshold
+import com.android.compose.swipeable.SwipeableState
+import com.android.compose.swipeable.ThresholdConfig
+import com.android.compose.swipeable.rememberSwipeableState
+import com.android.compose.swipeable.swipeable
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
+import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
+
+/**
+ * Renders a shade (container and content).
+ *
+ * This should be allowed to grow to fill the width and height of its container.
+ *
+ * @param viewModel The view-model for this shade.
+ * @param currentTimeMillis A provider for the current time, in milliseconds.
+ * @param containerHeightPx The height of the container that this shade is being shown in, in
+ * pixels.
+ * @param modifier The Modifier.
+ * @param content The content of the shade.
+ */
+@Composable
+fun Shade(
+ viewModel: ShadeViewModel,
+ currentTimeMillis: () -> Long,
+ containerHeightPx: Float,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit = {},
+) {
+ val isVisible: Boolean by viewModel.isVisible.collectAsState()
+ if (!isVisible) {
+ return
+ }
+
+ val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+ ReportNonProxiedInput(viewModel, interactionSource)
+
+ val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
+ HandleForcedCollapse(viewModel, swipeableState)
+ HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
+ ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
+
+ val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
+ val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
+ val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
+
+ val width: ShadeViewModel.Size by viewModel.width.collectAsState()
+ val density = LocalDensity.current
+
+ val anchors: Map<Float, ShadeState> =
+ remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
+
+ ShadeContent(
+ shadeHeightPx = { swipeableState.offset.value },
+ overstretch = { swipeableState.overflow.value / containerHeightPx },
+ isSwipingEnabled = isSwipingEnabled,
+ swipeableState = swipeableState,
+ interactionSource = interactionSource,
+ anchors = anchors,
+ thresholds = { _, to ->
+ swipeableThresholds(
+ to = to,
+ swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
+ swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
+ )
+ },
+ modifier = modifier.shadeWidth(width, density),
+ content = content,
+ )
+}
+
+/**
+ * Draws the content of the shade.
+ *
+ * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
+ * fully collapsed.
+ * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
+ * should be rendered with. This is `0` or a positive number that is a percentage of the total
+ * height of the shade when fully expanded. A value of `0` means that the shade is not stretched
+ * at all.
+ * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
+ * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
+ * addition to direct control (proxied user input in addition to non-proxied/direct user input).
+ * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
+ * occurs; this is used to configure the [swipeable] modifier.
+ * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
+ * another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
+ * user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
+ * @param content The content to render inside the shade.
+ * @param modifier The [Modifier].
+ */
+@Composable
+private fun ShadeContent(
+ shadeHeightPx: () -> Float,
+ overstretch: () -> Float,
+ isSwipingEnabled: Boolean,
+ swipeableState: SwipeableState<ShadeState>,
+ interactionSource: MutableInteractionSource,
+ anchors: Map<Float, ShadeState>,
+ thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit = {},
+) {
+ Surface(
+ shape = RoundedCornerShape(32.dp),
+ modifier =
+ modifier
+ .padding(12.dp)
+ .fillMaxWidth()
+ .height { shadeHeightPx().roundToInt() }
+ .graphicsLayer {
+ // Applies the vertical over-stretching of the shade content that may happen if
+ // the user keep dragging down when the shade is already fully-expanded.
+ transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
+ this.scaleY = 1 + overstretch().coerceAtLeast(0f)
+ }
+ .swipeable(
+ enabled = isSwipingEnabled,
+ state = swipeableState,
+ interactionSource = interactionSource,
+ anchors = anchors,
+ thresholds = thresholds,
+ orientation = Orientation.Vertical,
+ ),
+ content = content,
+ )
+}
+
+/** Funnels current shade expansion values into the view-model. */
+@Composable
+private fun ReportShadeExpansion(
+ viewModel: ShadeViewModel,
+ swipeableState: SwipeableState<ShadeState>,
+ containerHeightPx: Float,
+) {
+ LaunchedEffect(swipeableState.offset, containerHeightPx) {
+ snapshotFlow { swipeableState.offset.value / containerHeightPx }
+ .collect { expansion -> viewModel.onExpansionChanged(expansion) }
+ }
+}
+
+/** Funnels drag gesture start and end events into the view-model. */
+@Composable
+private fun ReportNonProxiedInput(
+ viewModel: ShadeViewModel,
+ interactionSource: InteractionSource,
+) {
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collect {
+ when (it) {
+ is DragInteraction.Start -> {
+ viewModel.onDragStarted()
+ }
+ is DragInteraction.Stop -> {
+ viewModel.onDragEnded()
+ }
+ }
+ }
+ }
+}
+
+/** When told to force collapse, collapses the shade. */
+@Composable
+private fun HandleForcedCollapse(
+ viewModel: ShadeViewModel,
+ swipeableState: SwipeableState<ShadeState>,
+) {
+ LaunchedEffect(viewModel) {
+ viewModel.isForceCollapsed.collect {
+ launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
+ }
+ }
+}
+
+/**
+ * Handles proxied input (input originating outside of the UI of the shade) by driving the
+ * [SwipeableState] accordingly.
+ */
+@Composable
+private fun HandleProxiedInput(
+ viewModel: ShadeViewModel,
+ swipeableState: SwipeableState<ShadeState>,
+ currentTimeMillis: () -> Long,
+) {
+ val velocityTracker: VelocityTracker = remember { VelocityTracker() }
+ LaunchedEffect(viewModel) {
+ viewModel.proxiedInput.collect {
+ when (it) {
+ is ProxiedInputModel.OnDrag -> {
+ velocityTracker.addPosition(
+ timeMillis = currentTimeMillis.invoke(),
+ position = Offset(0f, it.yDragAmountPx),
+ )
+ swipeableState.performDrag(it.yDragAmountPx)
+ }
+ is ProxiedInputModel.OnDragEnd -> {
+ launch {
+ val velocity = velocityTracker.calculateVelocity().y
+ velocityTracker.resetTracking()
+ // We use a VelocityTracker to keep a record of how fast the pointer was
+ // moving such that we know how far to fling the shade when the gesture
+ // ends. Flinging the SwipeableState using performFling is required after
+ // one or more calls to performDrag such that the swipeable settles into one
+ // of the states. Without doing that, the shade would remain unmoving in an
+ // in-between state on the screen.
+ swipeableState.performFling(velocity)
+ }
+ }
+ is ProxiedInputModel.OnDragCancel -> {
+ launch {
+ velocityTracker.resetTracking()
+ swipeableState.animateTo(swipeableState.progress.from)
+ }
+ }
+ else -> Unit
+ }
+ }
+ }
+}
+
+/**
+ * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
+ *
+ * @param density The [Density] of the display.
+ * @param wholePx The whole amount that the given [Float] is a fraction of.
+ * @return The dp size that's a fraction of the whole amount.
+ */
+private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
+ return with(density) { (this@fractionToDp * wholePx).toDp() }
+}
+
+private fun Modifier.shadeWidth(
+ size: ShadeViewModel.Size,
+ density: Density,
+): Modifier {
+ return then(
+ when (size) {
+ is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
+ is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
+ }
+ )
+}
+
+/** Returns the pixel positions for each of the supported shade states. */
+private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
+ return mapOf(
+ 0f to ShadeState.FullyCollapsed,
+ containerHeightPx to ShadeState.FullyExpanded,
+ )
+}
+
+/**
+ * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
+ * actually completes the expansion or collapse after the user lifts their pointer.
+ */
+private fun swipeableThresholds(
+ to: ShadeState,
+ swipeExpandThreshold: Dp,
+ swipeCollapseThreshold: Dp,
+): ThresholdConfig {
+ return FixedThreshold(
+ when (to) {
+ ShadeState.FullyExpanded -> swipeExpandThreshold
+ ShadeState.FullyCollapsed -> swipeCollapseThreshold
+ }
+ )
+}
+
+/** Enumerates the shade UI states for [SwipeableState]. */
+private enum class ShadeState {
+ FullyCollapsed,
+ FullyExpanded,
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
new file mode 100644
index 0000000..ca91b8a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Notifications(
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/272779828): implement.
+ Column(
+ modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ ) {
+ Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Spacer(modifier = Modifier.weight(1f))
+ Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
new file mode 100644
index 0000000..665d6dd
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.qs.footer.ui.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun QuickSettings(
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/272780058): implement.
+ Column(
+ modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ ) {
+ Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Spacer(modifier = Modifier.weight(1f))
+ Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
new file mode 100644
index 0000000..f514ab4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun StatusBar(
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/272780101): implement.
+ Row(
+ modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text("Status bar")
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 1d28c63..c0b69c1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -189,10 +189,12 @@
/** Get the text for secondaryLabel. */
public String getSecondaryLabel(String stateText) {
- if (TextUtils.isEmpty(secondaryLabel)) {
+ // Use a local reference as the value might change from other threads
+ CharSequence localSecondaryLabel = secondaryLabel;
+ if (TextUtils.isEmpty(localSecondaryLabel)) {
return stateText;
}
- return secondaryLabel.toString();
+ return localSecondaryLabel.toString();
}
public boolean copyTo(State other) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 70b5d73..b7088d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -50,6 +50,13 @@
boolean isPulsing();
/**
+ * Is device dreaming. This method is more inclusive than
+ * {@link android.service.dreams.IDreamManager.isDreaming}, as it will return true during the
+ * dream's wake-up phase.
+ */
+ boolean isDreaming();
+
+ /**
* Adds a state listener
*/
void addCallback(StateListener listener);
diff --git a/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
new file mode 100644
index 0000000..3b67ddd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2023, The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/dream_overlay_bottom_affordance_height"
+ android:height="@dimen/dream_overlay_bottom_affordance_width"/>
+ <corners android:radius="@dimen/dream_overlay_bottom_affordance_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml
index 7a628bb..642582c 100644
--- a/packages/SystemUI/res/drawable/ic_important_outline.xml
+++ b/packages/SystemUI/res/drawable/ic_important_outline.xml
@@ -20,6 +20,7 @@
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
+ android:autoMirrored="true"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index fb78b49..5b2ec48 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -22,13 +22,14 @@
<com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/home_controls_chip"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+ android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
android:layout_gravity="bottom|start"
- android:scaleType="center"
+ android:padding="@dimen/dream_overlay_bottom_affordance_padding"
+ android:background="@drawable/dream_overlay_bottom_affordance_bg"
+ android:scaleType="fitCenter"
android:tint="?android:attr/textColorPrimary"
android:src="@drawable/controls_icon"
- android:background="@drawable/keyguard_bottom_affordance_bg"
android:contentDescription="@string/quick_controls_title" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9d91419..85b6e8d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -97,7 +97,8 @@
android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false"
android:layout_width="wrap_content"
- android:layout_height="@dimen/min_clickable_item_size"
+ android:minHeight="@dimen/min_clickable_item_size"
+ android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml
new file mode 100644
index 0000000..78ff5f0
--- /dev/null
+++ b/packages/SystemUI/res/layout/multi_shade.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<com.android.systemui.multishade.ui.view.MultiShadeView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 4abc176..fe9542b 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -110,6 +110,13 @@
android:clipChildren="false"
android:clipToPadding="false" />
+ <ViewStub
+ android:id="@+id/multi_shade_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:inflatedId="@+id/multi_shade"
+ android:layout="@layout/multi_shade" />
+
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 311990c..e5cd0c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -844,4 +844,44 @@
<!-- Configuration to set Learn more in device logs as URL link -->
<bool name="log_access_confirmation_learn_more_as_link">true</bool>
+
+ <!-- [START] MULTI SHADE -->
+ <!-- Whether the device should use dual shade. If false, the device uses single shade. -->
+ <bool name="dual_shade_enabled">true</bool>
+ <!--
+ When in dual shade, where should the horizontal split be on the screen to help determine whether
+ the user is pulling down the left shade or the right shade. Must be between 0.0 and 1.0,
+ inclusive. In other words: how much of the left-hand side of the screen, when pulled down on,
+ would reveal the left-hand side shade.
+
+ More concretely:
+ A value of 0.67 means that the left two-thirds of the screen are dedicated to the left-hand side
+ shade and the remaining one-third of the screen on the right is dedicated to the right-hand side
+ shade.
+ -->
+ <dimen name="dual_shade_split_fraction">0.67</dimen>
+ <!-- Width of the left-hand side shade. -->
+ <dimen name="left_shade_width">436dp</dimen>
+ <!-- Width of the right-hand side shade. -->
+ <dimen name="right_shade_width">436dp</dimen>
+ <!--
+ Opaque version of the scrim that shows up behind dual shades. The alpha channel is driven
+ programmatically.
+ -->
+ <color name="opaque_scrim">#D9D9D9</color>
+ <!-- Maximum opacity when the scrim that shows up behind the dual shades is fully visible. -->
+ <dimen name="dual_shade_scrim_alpha">0.1</dimen>
+ <!--
+ The amount that the user must swipe down when the shade is fully collapsed to automatically
+ expand once the user lets go of the shade. If the user swipes less than this amount, the shade
+ will automatically revert back to fully collapsed once the user stops swiping.
+ -->
+ <dimen name="shade_swipe_expand_threshold">0.5</dimen>
+ <!--
+ The amount that the user must swipe up when the shade is fully expanded to automatically
+ collapse once the user lets go of the shade. If the user swipes less than this amount, the shade
+ will automatically revert back to fully expanded once the user stops swiping.
+ -->
+ <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
+ <!-- [END] MULTI SHADE -->
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bed8a80..4d38541 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1630,6 +1630,10 @@
<!-- Dream overlay complications related dimensions -->
<!-- The blur radius applied to the dream overlay when entering and exiting dreams -->
<dimen name="dream_overlay_anim_blur_radius">50dp</dimen>
+ <dimen name="dream_overlay_bottom_affordance_height">64dp</dimen>
+ <dimen name="dream_overlay_bottom_affordance_width">64dp</dimen>
+ <dimen name="dream_overlay_bottom_affordance_radius">32dp</dimen>
+ <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
<dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
<dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
<dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a02c429..ebf0f8e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2431,7 +2431,7 @@
<!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
- <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
+ <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g> can choose which controls and content show here.</string>
<!-- Shows in a dialog presented to the user to authorize this app removal from a Device
controls panel [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index 9766514..dedf0a7 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -53,6 +53,7 @@
fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+ val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
// disable drawing again if necessary once work is complete
if (!HardwareRendererCompat.isDrawingEnabled()) {
@@ -61,8 +62,12 @@
}
mainExecutor.execute {
- val forceRedrawFuture = forceRedraw()
- forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+ if (isRobolectric) {
+ generateBitmap(bitmapFuture)
+ } else {
+ val forceRedrawFuture = forceRedraw()
+ forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+ }
}
return bitmapFuture
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 2d47356..f96d1e3 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@
import android.app.Activity
import android.app.Dialog
import android.graphics.Bitmap
+import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
@@ -26,6 +27,7 @@
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.activity.ComponentActivity
import androidx.test.ext.junit.rules.ActivityScenarioRule
+import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@@ -54,14 +56,14 @@
)
)
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
- private val delegateRule =
- RuleChain.outerRule(colorsRule)
- .around(deviceEmulationRule)
- .around(screenshotRule)
- .around(activityRule)
+ private val roboRule =
+ RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
+ private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
+ private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
+ val ruleToApply = if (isRobolectric) roboRule else delegateRule
+ return ruleToApply.apply(base, description)
}
protected fun takeScreenshot(
@@ -94,7 +96,12 @@
contentView = content.getChildAt(0)
}
- return contentView?.toBitmap() ?: error("contentView is null")
+ return if (isRobolectric) {
+ contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
+ ?: error("timeout while trying to capture view to bitmap")
+ } else {
+ contentView?.toBitmap() ?: error("contentView is null")
+ }
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 6f7d66d..58e7747 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -321,7 +321,9 @@
} else {
// We are receiving new opening tasks, so convert to onTasksAppeared.
targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
- t.reparent(targets[i].leash, mInfo.getRootLeash());
+ // reparent into the original `mInfo` since that's where we are animating.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(targets[i].leash, layer);
mOpeningTasks.add(new TaskState(change, targets[i].leash));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 0e5f8c1..553453d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -18,13 +18,9 @@
import android.content.Context;
import android.content.res.TypedArray;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -33,22 +29,10 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
-import java.lang.ref.WeakReference;
-
/***
* Manages a number of views inside of the given layout. See below for a list of widgets.
*/
public abstract class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
- /** Handler token posted with accessibility announcement runnables. */
- private static final Object ANNOUNCE_TOKEN = new Object();
-
- /**
- * Delay before speaking an accessibility announcement. Used to prevent
- * lift-to-type from interrupting itself.
- */
- private static final long ANNOUNCEMENT_DELAY = 250;
-
- private final Handler mHandler;
private CharSequence mMessage;
private boolean mIsVisible;
@@ -65,7 +49,6 @@
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
- mHandler = new Handler(Looper.myLooper());
onThemeChanged();
}
@@ -127,9 +110,6 @@
private void securityMessageChanged(CharSequence message) {
mMessage = message;
update();
- mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
- mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
- (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
}
private void clearMessage() {
@@ -156,25 +136,4 @@
/** Set the text color */
protected abstract void updateTextColor();
-
- /**
- * Runnable used to delay accessibility announcements.
- */
- private static class AnnounceRunnable implements Runnable {
- private final WeakReference<View> mHost;
- private final CharSequence mTextToAnnounce;
-
- AnnounceRunnable(View host, CharSequence textToAnnounce) {
- mHost = new WeakReference<View>(host);
- mTextToAnnounce = textToAnnounce;
- }
-
- @Override
- public void run() {
- final View host = mHost.get();
- if (host != null) {
- host.announceForAccessibility(mTextToAnnounce);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 6a92162..c1896fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,11 +18,17 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.ViewController;
+import java.lang.ref.WeakReference;
+
import javax.inject.Inject;
/**
@@ -31,8 +37,14 @@
*/
public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
extends ViewController<T> {
+ /**
+ * Delay before speaking an accessibility announcement. Used to prevent
+ * lift-to-type from interrupting itself.
+ */
+ private static final long ANNOUNCEMENT_DELAY = 250;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
+ private final AnnounceRunnable mAnnounceRunnable;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -68,6 +80,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
+ mAnnounceRunnable = new AnnounceRunnable(mView);
}
@Override
@@ -100,6 +113,12 @@
*/
public void setMessage(CharSequence s, boolean animate) {
mView.setMessage(s, animate);
+ CharSequence msg = mView.getText();
+ if (!TextUtils.isEmpty(msg)) {
+ mView.removeCallbacks(mAnnounceRunnable);
+ mAnnounceRunnable.setTextToAnnounce(msg);
+ mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY);
+ }
}
public void setMessage(int resId) {
@@ -134,4 +153,30 @@
view, mKeyguardUpdateMonitor, mConfigurationController);
}
}
+
+ /**
+ * Runnable used to delay accessibility announcements.
+ */
+ @VisibleForTesting
+ public static class AnnounceRunnable implements Runnable {
+ private final WeakReference<View> mHost;
+ private CharSequence mTextToAnnounce;
+
+ AnnounceRunnable(View host) {
+ mHost = new WeakReference<>(host);
+ }
+
+ /** Sets the text to announce. */
+ public void setTextToAnnounce(CharSequence textToAnnounce) {
+ mTextToAnnounce = textToAnnounce;
+ }
+
+ @Override
+ public void run() {
+ final View host = mHost.get();
+ if (host != null) {
+ host.announceForAccessibility(mTextToAnnounce);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 98866c6..7255383 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -1066,23 +1066,28 @@
}
private void reloadColors() {
- reinflateViewFlipper();
- mView.reloadColors();
+ reinflateViewFlipper(() -> mView.reloadColors());
}
/** Handles density or font scale changes. */
private void onDensityOrFontScaleChanged() {
- reinflateViewFlipper();
- mView.onDensityOrFontScaleChanged();
+ reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged());
}
/**
* Reinflate the view flipper child view.
*/
- public void reinflateViewFlipper() {
+ public void reinflateViewFlipper(
+ KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) {
mSecurityViewFlipperController.clearViews();
- mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
- mKeyguardSecurityCallback);
+ if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) {
+ mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode,
+ mKeyguardSecurityCallback, onViewInflatedListener);
+ } else {
+ mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+ mKeyguardSecurityCallback);
+ onViewInflatedListener.onViewInflated();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 39b567f..68e1dd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -19,11 +19,16 @@
import android.util.Log;
import android.view.LayoutInflater;
+import androidx.annotation.Nullable;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardInputViewController.Factory;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
@@ -44,18 +49,24 @@
private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
new ArrayList<>();
private final LayoutInflater mLayoutInflater;
+ private final AsyncLayoutInflater mAsyncLayoutInflater;
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final Factory mKeyguardSecurityViewControllerFactory;
+ private final FeatureFlags mFeatureFlags;
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
+ AsyncLayoutInflater asyncLayoutInflater,
KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
- EmergencyButtonController.Factory emergencyButtonControllerFactory) {
+ EmergencyButtonController.Factory emergencyButtonControllerFactory,
+ FeatureFlags featureFlags) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
+ mAsyncLayoutInflater = asyncLayoutInflater;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -92,13 +103,12 @@
}
}
- if (childController == null
+ if (!mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER) && childController == null
&& securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
-
int layoutId = getLayoutIdFor(securityMode);
KeyguardInputView view = null;
if (layoutId != 0) {
- if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+ if (DEBUG) Log.v(TAG, "inflating on main thread id = " + layoutId);
view = (KeyguardInputView) mLayoutInflater.inflate(
layoutId, mView, false);
mView.addView(view);
@@ -119,6 +129,36 @@
return childController;
}
+ /**
+ * Asynchronously inflate view and then add it to view flipper on the main thread when complete.
+ *
+ * OnInflateFinishedListener will be called on the main thread.
+ *
+ * @param securityMode
+ * @param keyguardSecurityCallback
+ */
+ public void asynchronouslyInflateView(SecurityMode securityMode,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ @Nullable OnViewInflatedListener onViewInflatedListener) {
+ int layoutId = getLayoutIdFor(securityMode);
+ if (layoutId != 0) {
+ if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId);
+ mAsyncLayoutInflater.inflate(layoutId, mView,
+ (view, resId, parent) -> {
+ mView.addView(view);
+ KeyguardInputViewController<KeyguardInputView> childController =
+ mKeyguardSecurityViewControllerFactory.create(
+ (KeyguardInputView) view, securityMode,
+ keyguardSecurityCallback);
+ childController.init();
+ mChildren.add(childController);
+ if (onViewInflatedListener != null) {
+ onViewInflatedListener.onViewInflated();
+ }
+ });
+ }
+ }
+
private int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
@@ -162,4 +202,10 @@
return 0;
}
}
+
+ /** Listener to when view has finished inflation. */
+ public interface OnViewInflatedListener {
+ /** Notifies that view has been inflated */
+ void onViewInflated();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 9c847be..08236b7 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -61,3 +61,8 @@
## 4: SYSTEM_REGISTER_USER System sysui registers user's callbacks
## 5: SYSTEM_UNREGISTER_USER System sysui unregisters user's callbacks (after death)
36060 sysui_recents_connection (type|1),(user|1)
+
+# ---------------------------
+# KeyguardViewMediator.java
+# ---------------------------
+36080 sysui_keyguard (isOccluded|1),(animate|1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c31d45f..4aa985b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -198,32 +198,36 @@
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mCurrentDialog != null
- && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
String reason = intent.getStringExtra("reason");
reason = (reason != null) ? reason : "unknown";
- Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
-
- mCurrentDialog.dismissWithoutCallback(true /* animate */);
- mCurrentDialog = null;
-
- for (Callback cb : mCallbacks) {
- cb.onBiometricPromptDismissed();
- }
-
- try {
- if (mReceiver != null) {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
- mReceiver = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception", e);
- }
+ closeDioalog(reason);
}
}
};
+ private void closeDioalog(String reason) {
+ if (isShowing()) {
+ Log.i(TAG, "Close BP, reason :" + reason);
+ mCurrentDialog.dismissWithoutCallback(true /* animate */);
+ mCurrentDialog = null;
+
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
+ try {
+ if (mReceiver != null) {
+ mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+ null /* credentialAttestation */);
+ mReceiver = null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception", e);
+ }
+ }
+ }
+
private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
if (mCurrentDialog != null) {
@@ -546,6 +550,11 @@
}
}
+ @Override
+ public void handleShowGlobalActionsMenu() {
+ closeDioalog("PowerMenu shown");
+ }
+
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index c0f8549..4173bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,8 +21,10 @@
import android.view.View
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
/**
* A facade to interact with Compose, when it is available.
@@ -57,4 +59,11 @@
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
): View
+
+ /** Create a [View] to represent [viewModel] on screen. */
+ fun createMultiShadeView(
+ context: Context,
+ viewModel: MultiShadeViewModel,
+ clock: SystemClock,
+ ): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index 06d4a08..ce0f2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -155,17 +155,18 @@
d.show()
}
- private fun turnOnSettingSecurely(settings: List<String>) {
+ private fun turnOnSettingSecurely(settings: List<String>, onComplete: () -> Unit) {
val action =
ActivityStarter.OnDismissAction {
settings.forEach { setting ->
secureSettings.putIntForUser(setting, 1, userTracker.userId)
}
+ onComplete()
true
}
activityStarter.dismissKeyguardThenExecute(
action,
- /* cancel */ null,
+ /* cancel */ onComplete,
/* afterKeyguardGone */ true
)
}
@@ -186,7 +187,11 @@
if (!showDeviceControlsInLockscreen) {
settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
}
- turnOnSettingSecurely(settings)
+ // If we are toggling the flag, we want to call onComplete after the keyguard is
+ // dismissed (and the setting is turned on), to pass the correct value.
+ turnOnSettingSecurely(settings, onComplete)
+ } else {
+ onComplete()
}
if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs
@@ -194,7 +199,6 @@
.putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
.apply()
}
- onComplete()
}
override fun onCancel(dialog: DialogInterface?) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index bf0a692..224eb1c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -20,6 +20,8 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
import android.os.Bundle
import android.os.RemoteException
import android.service.dreams.IDreamManager
@@ -57,9 +59,11 @@
private lateinit var parent: ViewGroup
private lateinit var broadcastReceiver: BroadcastReceiver
private var mExitToDream: Boolean = false
+ private lateinit var lastConfiguration: Configuration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ lastConfiguration = resources.configuration
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
}
@@ -92,6 +96,14 @@
initBroadcastReceiver()
}
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ if (lastConfiguration.diff(newConfig) and ActivityInfo.CONFIG_ORIENTATION != 0 ) {
+ uiController.onOrientationChange()
+ }
+ lastConfiguration = newConfig
+ }
+
override fun onStart() {
super.onStart()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 0d53117..3ecf423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -64,6 +64,8 @@
* This element will be the one that appears when the user first opens the controls activity.
*/
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
+
+ fun onOrientationChange()
}
sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 5da86de9..ee12db8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -124,6 +124,7 @@
}
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
+ private var selectionItem: SelectionItem? = null
private lateinit var allStructures: List<StructureInfo>
private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
@@ -230,6 +231,7 @@
this.overflowMenuAdapter = null
hidden = false
retainCache = false
+ selectionItem = null
controlActionCoordinator.activityContext = activityContext
@@ -272,7 +274,7 @@
}
}
- private fun reload(parent: ViewGroup) {
+ private fun reload(parent: ViewGroup, dismissTaskView: Boolean = true) {
if (hidden) return
controlsListingController.get().removeCallback(listingCallback)
@@ -327,8 +329,8 @@
@VisibleForTesting
internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) {
removeAppDialog?.cancel()
- removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) {
- if (!controlsController.get().removeFavorites(componentName)) {
+ removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) { shouldRemove ->
+ if (!shouldRemove || !controlsController.get().removeFavorites(componentName)) {
return@createRemoveAppDialog
}
@@ -425,6 +427,7 @@
} else {
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
}
+ this.selectionItem = selectionItem
bgExecutor.execute {
val intent = Intent(Intent.ACTION_MAIN)
@@ -657,6 +660,7 @@
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
+ listView.removeAllViews()
var lastRow: ViewGroup = createRow(inflater, listView)
selectedStructure.controls.forEach {
val key = ControlKey(selectedStructure.componentName, it.controlId)
@@ -804,6 +808,15 @@
}
}
+ override fun onOrientationChange() {
+ selectionItem?.let {
+ when (selectedItem) {
+ is SelectedItem.StructureItem -> createListView(it)
+ is SelectedItem.PanelItem -> taskViewController?.refreshBounds() ?: reload(parent)
+ }
+ } ?: reload(parent)
+ }
+
private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup {
val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup
listView.addView(row)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 78e87ca..1f89c91 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -37,7 +37,7 @@
private val activityContext: Context,
private val uiExecutor: Executor,
private val pendingIntent: PendingIntent,
- private val taskView: TaskView,
+ val taskView: TaskView,
private val hide: () -> Unit = {}
) {
@@ -108,6 +108,10 @@
}
}
+ fun refreshBounds() {
+ taskView.onLocationChanged()
+ }
+
fun dismiss() {
taskView.release()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b054c7e..0be3bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -98,6 +98,7 @@
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.app.NotificationManagerCompat;
import com.android.internal.app.IBatteryStats;
@@ -395,6 +396,13 @@
return LayoutInflater.from(context);
}
+ /** */
+ @Provides
+ @Singleton
+ public AsyncLayoutInflater provideAsyncLayoutInflater(Context context) {
+ return new AsyncLayoutInflater(context);
+ }
+
@Provides
static MediaProjectionManager provideMediaProjectionManager(Context context) {
return context.getSystemService(MediaProjectionManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 9374ad9..471c445 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -156,6 +156,7 @@
ComponentName lowLightDreamComponent,
DreamOverlayCallbackController dreamOverlayCallbackController,
@Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
+ super(executor);
mContext = context;
mExecutor = executor;
mWindowManager = windowManager;
@@ -202,55 +203,50 @@
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- mExecutor.execute(() -> {
- setCurrentStateLocked(Lifecycle.State.STARTED);
+ setCurrentStateLocked(Lifecycle.State.STARTED);
- mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
- if (mDestroyed) {
- // The task could still be executed after the service has been destroyed. Bail if
- // that is the case.
- return;
- }
+ if (mDestroyed) {
+ // The task could still be executed after the service has been destroyed. Bail if
+ // that is the case.
+ return;
+ }
- if (mStarted) {
- // Reset the current dream overlay before starting a new one. This can happen
- // when two dreams overlap (briefly, for a smoother dream transition) and both
- // dreams are bound to the dream overlay service.
- resetCurrentDreamOverlayLocked();
- }
+ if (mStarted) {
+ // Reset the current dream overlay before starting a new one. This can happen
+ // when two dreams overlap (briefly, for a smoother dream transition) and both
+ // dreams are bound to the dream overlay service.
+ resetCurrentDreamOverlayLocked();
+ }
- mDreamOverlayContainerViewController =
- mDreamOverlayComponent.getDreamOverlayContainerViewController();
- mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
- mDreamOverlayTouchMonitor.init();
+ mDreamOverlayContainerViewController =
+ mDreamOverlayComponent.getDreamOverlayContainerViewController();
+ mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
+ mDreamOverlayTouchMonitor.init();
- mStateController.setShouldShowComplications(shouldShowComplications());
+ mStateController.setShouldShowComplications(shouldShowComplications());
- // If we are not able to add the overlay window, reset the overlay.
- if (!addOverlayWindowLocked(layoutParams)) {
- resetCurrentDreamOverlayLocked();
- return;
- }
+ // If we are not able to add the overlay window, reset the overlay.
+ if (!addOverlayWindowLocked(layoutParams)) {
+ resetCurrentDreamOverlayLocked();
+ return;
+ }
+ setCurrentStateLocked(Lifecycle.State.RESUMED);
+ mStateController.setOverlayActive(true);
+ final ComponentName dreamComponent = getDreamComponent();
+ mStateController.setLowLightActive(
+ dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
- setCurrentStateLocked(Lifecycle.State.RESUMED);
- mStateController.setOverlayActive(true);
- final ComponentName dreamComponent = getDreamComponent();
- mStateController.setLowLightActive(
- dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
- mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
-
- mDreamOverlayCallbackController.onStartDream();
- mStarted = true;
- });
+ mDreamOverlayCallbackController.onStartDream();
+ mStarted = true;
}
@Override
public void onEndDream() {
- mExecutor.execute(() -> {
- resetCurrentDreamOverlayLocked();
- });
+ resetCurrentDreamOverlayLocked();
}
private Lifecycle.State getCurrentStateLocked() {
@@ -263,12 +259,10 @@
@Override
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
- mExecutor.execute(() -> {
- if (mDreamOverlayContainerViewController != null) {
- mDreamOverlayCallbackController.onWakeUp();
- mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
- }
- });
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayCallbackController.onWakeUp();
+ mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index 244212b..1702eac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -75,6 +75,10 @@
Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
settingsObserver,
UserHandle.myUserId());
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
+ settingsObserver,
+ UserHandle.myUserId());
settingsObserver.onChange(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 9b954f5f..616bd81 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -23,6 +23,8 @@
import com.android.systemui.dagger.SystemUIBinder;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import javax.inject.Named;
@@ -47,17 +49,27 @@
String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params";
int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+ int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2;
int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2;
int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
+ int DREAM_WEATHER_COMPLICATION_WEIGHT = 0;
/**
* Provides layout parameters for the clock time complication.
*/
@Provides
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideClockTimeLayoutParams() {
+ static ComplicationLayoutParams provideClockTimeLayoutParams(FeatureFlags featureFlags) {
+ if (featureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)) {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE);
+ }
return new ComplicationLayoutParams(0,
ViewGroup.LayoutParams.WRAP_CONTENT,
ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 63f63a5..78e132f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -30,14 +30,15 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
-import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -56,13 +57,16 @@
@Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
- @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
+ @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+ @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+ optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
) {
companion object {
private const val TAG = "DreamSmartspaceCtrlr"
}
private var session: SmartspaceSession? = null
+ private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -116,31 +120,54 @@
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
+ // The weather data plugin takes unfiltered targets and performs the filtering internally.
+ weatherPlugin?.onTargetsAvailable(targets)
+
onTargetsAvailableUnfiltered(targets)
val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
plugin?.onTargetsAvailable(filteredTargets)
}
/**
+ * Constructs the weather view with custom layout and connects it to the weather plugin.
+ */
+ fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
+ return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
+ }
+
+ /**
* Constructs the smartspace view and connects it to the smartspace service.
*/
fun buildAndConnectView(parent: ViewGroup): View? {
+ return buildAndConnectViewWithPlugin(parent, plugin, null)
+ }
+
+ private fun buildAndConnectViewWithPlugin(
+ parent: ViewGroup,
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+ customView: View?
+ ): View? {
execution.assertIsMainThread()
if (!precondition.conditionsMet()) {
throw RuntimeException("Cannot build view when not enabled")
}
- val view = buildView(parent)
+ val view = buildView(parent, smartspaceDataPlugin, customView)
connectSession()
return view
}
- private fun buildView(parent: ViewGroup): View? {
- return if (plugin != null) {
- var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
+ private fun buildView(
+ parent: ViewGroup,
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+ customView: View?
+ ): View? {
+ return if (smartspaceDataPlugin != null) {
+ val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
+ stateChangeListener, customView)
.getView()
if (view !is View) {
return null
@@ -157,7 +184,10 @@
}
private fun connectSession() {
- if (plugin == null || session != null || !hasActiveSessionListeners()) {
+ if (plugin == null && weatherPlugin == null) {
+ return
+ }
+ if (session != null || !hasActiveSessionListeners()) {
return
}
@@ -166,13 +196,14 @@
}
val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "dream").build()
+ SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
)
Log.d(TAG, "Starting smartspace session for dream")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
- plugin.registerSmartspaceEventNotifier {
+ weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+ plugin?.registerSmartspaceEventNotifier {
e ->
session?.notifySmartspaceEvent(e)
}
@@ -199,22 +230,47 @@
session = null
+ weatherPlugin?.registerSmartspaceEventNotifier(null)
+ weatherPlugin?.onTargetsAvailable(emptyList())
+
plugin?.registerSmartspaceEventNotifier(null)
plugin?.onTargetsAvailable(emptyList())
Log.d(TAG, "Ending smartspace session for dream")
}
fun addListener(listener: SmartspaceTargetListener) {
+ addAndRegisterListener(listener, plugin)
+ }
+
+ fun removeListener(listener: SmartspaceTargetListener) {
+ removeAndUnregisterListener(listener, plugin)
+ }
+
+ fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+ addAndRegisterListener(listener, weatherPlugin)
+ }
+
+ fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+ removeAndUnregisterListener(listener, weatherPlugin)
+ }
+
+ private fun addAndRegisterListener(
+ listener: SmartspaceTargetListener,
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ ) {
execution.assertIsMainThread()
- plugin?.registerListener(listener)
+ smartspaceDataPlugin?.registerListener(listener)
listeners.add(listener)
connectSession()
}
- fun removeListener(listener: SmartspaceTargetListener) {
+ private fun removeAndUnregisterListener(
+ listener: SmartspaceTargetListener,
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ ) {
execution.assertIsMainThread()
- plugin?.unregisterListener(listener)
+ smartspaceDataPlugin?.unregisterListener(listener)
listeners.remove(listener)
disconnect()
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 5c310c3..2c11d78 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -86,9 +86,34 @@
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@Override
- public void onChange(Flag<?> flag) {
- mRestarter.restartSystemUI(
- "Server flag change: " + flag.getNamespace() + "." + flag.getName());
+ public void onChange(Flag<?> flag, String value) {
+ boolean shouldRestart = false;
+ if (mBooleanFlagCache.containsKey(flag.getName())) {
+ boolean newValue = value == null ? false : Boolean.parseBoolean(value);
+ if (mBooleanFlagCache.get(flag.getName()) != newValue) {
+ shouldRestart = true;
+ }
+ } else if (mStringFlagCache.containsKey(flag.getName())) {
+ String newValue = value == null ? "" : value;
+ if (mStringFlagCache.get(flag.getName()) != value) {
+ shouldRestart = true;
+ }
+ } else if (mIntFlagCache.containsKey(flag.getName())) {
+ int newValue = 0;
+ try {
+ newValue = value == null ? 0 : Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ }
+ if (mIntFlagCache.get(flag.getName()) != newValue) {
+ shouldRestart = true;
+ }
+ }
+ if (shouldRestart) {
+ mRestarter.restartSystemUI(
+ "Server flag change: " + flag.getNamespace() + "."
+ + flag.getName());
+
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 9859ff6..9d19a7d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -53,13 +53,38 @@
private final Map<String, Flag<?>> mAllFlags;
private final Map<String, Boolean> mBooleanCache = new HashMap<>();
private final Map<String, String> mStringCache = new HashMap<>();
+ private final Map<String, Integer> mIntCache = new HashMap<>();
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@Override
- public void onChange(Flag<?> flag) {
- mRestarter.restartSystemUI(
- "Server flag change: " + flag.getNamespace() + "." + flag.getName());
+ public void onChange(Flag<?> flag, String value) {
+ boolean shouldRestart = false;
+ if (mBooleanCache.containsKey(flag.getName())) {
+ boolean newValue = value == null ? false : Boolean.parseBoolean(value);
+ if (mBooleanCache.get(flag.getName()) != newValue) {
+ shouldRestart = true;
+ }
+ } else if (mStringCache.containsKey(flag.getName())) {
+ String newValue = value == null ? "" : value;
+ if (mStringCache.get(flag.getName()) != newValue) {
+ shouldRestart = true;
+ }
+ } else if (mIntCache.containsKey(flag.getName())) {
+ int newValue = 0;
+ try {
+ newValue = value == null ? 0 : Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ }
+ if (mIntCache.get(flag.getName()) != newValue) {
+ shouldRestart = true;
+ }
+ }
+ if (shouldRestart) {
+ mRestarter.restartSystemUI(
+ "Server flag change: " + flag.getNamespace() + "."
+ + flag.getName());
+ }
}
};
@@ -97,68 +122,97 @@
@Override
public boolean isEnabled(@NotNull ReleasedFlag flag) {
- return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true);
+ // Fill the cache.
+ return isEnabledInternal(flag.getName(),
+ mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true));
}
@Override
public boolean isEnabled(ResourceBooleanFlag flag) {
- if (!mBooleanCache.containsKey(flag.getName())) {
- return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId()));
- }
-
- return mBooleanCache.get(flag.getName());
+ // Fill the cache.
+ return isEnabledInternal(flag.getName(), mResources.getBoolean(flag.getResourceId()));
}
@Override
public boolean isEnabled(SysPropBooleanFlag flag) {
- if (!mBooleanCache.containsKey(flag.getName())) {
- return isEnabled(
- flag.getName(),
- mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
- }
-
- return mBooleanCache.get(flag.getName());
+ // Fill the cache.
+ return isEnabledInternal(
+ flag.getName(),
+ mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
}
- private boolean isEnabled(String name, boolean defaultValue) {
- mBooleanCache.put(name, defaultValue);
- return defaultValue;
+ /**
+ * Checks and fills the boolean cache. This is important, Always call through to this method!
+ *
+ * We use the cache as a way to decide if we need to restart the process when server-side
+ * changes occur.
+ */
+ private boolean isEnabledInternal(String name, boolean defaultValue) {
+ // Fill the cache.
+ if (!mBooleanCache.containsKey(name)) {
+ mBooleanCache.put(name, defaultValue);
+ }
+
+ return mBooleanCache.get(name);
}
@NonNull
@Override
public String getString(@NonNull StringFlag flag) {
- return getString(flag.getName(), flag.getDefault());
+ // Fill the cache.
+ return getStringInternal(flag.getName(), flag.getDefault());
}
@NonNull
@Override
public String getString(@NonNull ResourceStringFlag flag) {
- if (!mStringCache.containsKey(flag.getName())) {
- return getString(flag.getName(),
- requireNonNull(mResources.getString(flag.getResourceId())));
- }
-
- return mStringCache.get(flag.getName());
+ // Fill the cache.
+ return getStringInternal(flag.getName(),
+ requireNonNull(mResources.getString(flag.getResourceId())));
}
- private String getString(String name, String defaultValue) {
- mStringCache.put(name, defaultValue);
- return defaultValue;
+ /**
+ * Checks and fills the String cache. This is important, Always call through to this method!
+ *
+ * We use the cache as a way to decide if we need to restart the process when server-side
+ * changes occur.
+ */
+ private String getStringInternal(String name, String defaultValue) {
+ if (!mStringCache.containsKey(name)) {
+ mStringCache.put(name, defaultValue);
+ }
+
+ return mStringCache.get(name);
}
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
- return flag.getDefault();
+ // Fill the cache.
+ return getIntInternal(flag.getName(), flag.getDefault());
}
@NonNull
@Override
public int getInt(@NonNull ResourceIntFlag flag) {
+ // Fill the cache.
return mResources.getInteger(flag.getResourceId());
}
+ /**
+ * Checks and fills the integer cache. This is important, Always call through to this method!
+ *
+ * We use the cache as a way to decide if we need to restart the process when server-side
+ * changes occur.
+ */
+ private int getIntInternal(String name, int defaultValue) {
+ if (!mIntCache.containsKey(name)) {
+ mIntCache.put(name, defaultValue);
+ }
+
+ return mIntCache.get(name);
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: false");
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b611f46..2666d94 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -244,6 +244,11 @@
@JvmField
val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
+ // TODO(b/271460958): Tracking Bug
+ @JvmField
+ val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405,
+ "show_weather_complication_on_dream_overlay")
+
// 500 - quick settings
val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@@ -591,8 +596,7 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// TODO(b/267162944): Tracking bug
- @JvmField
- val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model", teamfood = true)
+ @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
@@ -676,4 +680,9 @@
@JvmField
val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true)
+
+ // TODO(b/272805037): Tracking Bug
+ @JvmField
+ val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
+ namespace = "vpn", teamfood = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 9b748d0..eaf5eac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -37,7 +37,7 @@
fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
interface ChangeListener {
- fun onChange(flag: Flag<*>)
+ fun onChange(flag: Flag<*>, value: String?)
}
}
@@ -67,7 +67,7 @@
propLoop@ for (propName in properties.keyset) {
for (flag in flags) {
if (propName == flag.name) {
- listener.onChange(flag)
+ listener.onChange(flag, properties.getString(propName, null))
break@propLoop
}
}
@@ -144,7 +144,7 @@
for ((listener, flags) in listeners) {
flagLoop@ for (flag in flags) {
if (name == flag.name) {
- listener.onChange(flag)
+ listener.onChange(flag, if (value) "true" else "false")
break@flagLoop
}
}
@@ -159,5 +159,6 @@
flags: Collection<Flag<*>>,
listener: ServerFlagReader.ChangeListener
) {
+ listeners.add(Pair(listener, flags))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 377a136..5ecc00f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -118,6 +118,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
@@ -1849,6 +1850,8 @@
private void handleSetOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
+ EventLogTags.writeSysuiKeyguard(isOccluded ? 1 : 0, animate ? 1 : 0);
+
mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
synchronized (KeyguardViewMediator.this) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 450fa14..82be009 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -176,10 +176,10 @@
return;
}
- final Intent credential = getKeyguardManager()
+ final Intent confirmCredentialIntent = getKeyguardManager()
.createConfirmDeviceCredentialIntent(null, null, getTargetUserId(),
true /* disallowBiometricsIfPolicyExists */);
- if (credential == null) {
+ if (confirmCredentialIntent == null) {
return;
}
@@ -193,14 +193,18 @@
PendingIntent.FLAG_IMMUTABLE, options.toBundle());
if (target != null) {
- credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+ confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
}
+ // WorkLockActivity is started as a task overlay, so unless credential confirmation is also
+ // started as an overlay, it won't be visible.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchTaskId(getTaskId());
launchOptions.setTaskOverlay(true /* taskOverlay */, true /* canResume */);
+ // Propagate it in case more than one activity is launched.
+ confirmCredentialIntent.putExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, true);
- startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS,
+ startActivityForResult(confirmCredentialIntent, REQUEST_CODE_CONFIRM_CREDENTIALS,
launchOptions.toBundle());
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index a2589d3..871a3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -55,4 +55,5 @@
fun willRunDismissFromKeyguard(): Boolean
/** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
fun getBackCallback(): OnBackAnimationCallback
+ fun showPromptReason(reason: Int)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 86e5cd7..ae5b799 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -22,7 +22,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.log.dagger.BouncerLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
@@ -43,10 +42,8 @@
*/
interface KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
- val primaryBouncerVisible: StateFlow<Boolean>
- val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+ val primaryBouncerShow: StateFlow<Boolean>
val primaryBouncerShowingSoon: StateFlow<Boolean>
- val primaryBouncerHide: StateFlow<Boolean>
val primaryBouncerStartingToHide: StateFlow<Boolean>
val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
@@ -76,14 +73,10 @@
fun setPrimaryScrimmed(isScrimmed: Boolean)
- fun setPrimaryVisible(isVisible: Boolean)
-
- fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+ fun setPrimaryShow(isShowing: Boolean)
fun setPrimaryShowingSoon(showingSoon: Boolean)
- fun setPrimaryHide(hide: Boolean)
-
fun setPrimaryStartingToHide(startingToHide: Boolean)
fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
@@ -117,14 +110,10 @@
@BouncerLog private val buffer: TableLogBuffer,
) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
- private val _primaryBouncerVisible = MutableStateFlow(false)
- override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
- private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+ private val _primaryBouncerShow = MutableStateFlow(false)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
- private val _primaryBouncerHide = MutableStateFlow(false)
- override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
@@ -177,10 +166,6 @@
_primaryBouncerScrimmed.value = isScrimmed
}
- override fun setPrimaryVisible(isVisible: Boolean) {
- _primaryBouncerVisible.value = isVisible
- }
-
override fun setAlternateVisible(isVisible: Boolean) {
if (isVisible && !_alternateBouncerVisible.value) {
lastAlternateBouncerVisibleTime = clock.uptimeMillis()
@@ -194,18 +179,14 @@
_alternateBouncerUIAvailable.value = isAvailable
}
- override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
- _primaryBouncerShow.value = keyguardBouncerModel
+ override fun setPrimaryShow(isShowing: Boolean) {
+ _primaryBouncerShow.value = isShowing
}
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
- override fun setPrimaryHide(hide: Boolean) {
- _primaryBouncerHide.value = hide
- }
-
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
@@ -248,19 +229,12 @@
return
}
- primaryBouncerVisible
- .logDiffsForTable(buffer, "", "PrimaryBouncerVisible", false)
- .launchIn(applicationScope)
primaryBouncerShow
- .map { it != null }
.logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
.launchIn(applicationScope)
primaryBouncerShowingSoon
.logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
.launchIn(applicationScope)
- primaryBouncerHide
- .logDiffsForTable(buffer, "", "PrimaryBouncerHide", false)
- .launchIn(applicationScope)
primaryBouncerStartingToHide
.logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
.launchIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c42e502..1ac0c52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -137,7 +137,7 @@
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Whether the primary bouncer is showing or not. */
- val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerVisible
+ val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
/** Whether the alternate bouncer is showing or not. */
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/** Observable for the [StatusBarState] */
@@ -159,7 +159,7 @@
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
combine(
isKeyguardVisible,
- bouncerRepository.primaryBouncerVisible,
+ primaryBouncerShowing,
onCameraLaunchDetected,
) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent ->
when {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 3d2c472..b10aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -21,12 +21,15 @@
import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
-import android.view.View
+import android.os.UserHandle
+import android.os.UserManager
import android.util.Log
+import android.view.View
import com.android.keyguard.KeyguardConstants
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.Utils
import com.android.systemui.DejankUtils
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
@@ -37,7 +40,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -81,23 +83,21 @@
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
- repository.setPrimaryVisible(true)
- repository.setPrimaryShow(
- KeyguardBouncerModel(
- promptReason = repository.bouncerPromptReason ?: 0,
- errorMessage = repository.bouncerErrorMessage,
- expansionAmount = repository.panelExpansionAmount.value
+ repository.setPrimaryShow(true)
+ primaryBouncerView.delegate?.showPromptReason(repository.bouncerPromptReason)
+ (repository.bouncerErrorMessage as? String)?.let {
+ repository.setShowMessage(
+ BouncerShowMessageModel(message = it, Utils.getColorError(context))
)
- )
+ }
repository.setPrimaryShowingSoon(false)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
- val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
- val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
+ val show: Flow<Unit> = repository.primaryBouncerShow.filter { it }.map {}
+ val hide: Flow<Unit> = repository.primaryBouncerShow.filter { !it }.map {}
val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
- val isVisible: Flow<Boolean> = repository.primaryBouncerVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
@@ -107,10 +107,11 @@
val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
/** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
val bouncerExpansion: Flow<Float> =
- combine(repository.panelExpansionAmount, repository.primaryBouncerVisible) {
- panelExpansion,
- primaryBouncerVisible ->
- if (primaryBouncerVisible) {
+ combine(
+ repository.panelExpansionAmount,
+ repository.primaryBouncerShow
+ ) { panelExpansion, primaryBouncerIsShowing ->
+ if (primaryBouncerIsShowing) {
1f - panelExpansion
} else {
0f
@@ -120,21 +121,20 @@
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
- /**
- * This callback needs to be a class field so it does not get garbage collected.
- */
- val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricRunningStateChanged(
- running: Boolean,
- biometricSourceType: BiometricSourceType?
- ) {
- updateSideFpsVisibility()
- }
+ /** This callback needs to be a class field so it does not get garbage collected. */
+ val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricRunningStateChanged(
+ running: Boolean,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ updateSideFpsVisibility()
+ }
- override fun onStrongAuthStateChanged(userId: Int) {
- updateSideFpsVisibility()
+ override fun onStrongAuthStateChanged(userId: Int) {
+ updateSideFpsVisibility()
+ }
}
- }
init {
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
@@ -147,14 +147,13 @@
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
repository.setKeyguardAuthenticated(null)
- repository.setPrimaryHide(false)
repository.setPrimaryStartingToHide(false)
val resumeBouncer =
- (repository.primaryBouncerVisible.value ||
- repository.primaryBouncerShowingSoon.value) && needsFullscreenBouncer()
+ (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
+ needsFullscreenBouncer()
- if (!resumeBouncer && repository.primaryBouncerShow.value != null) {
+ if (!resumeBouncer && isBouncerShowing()) {
// If bouncer is visible, the bouncer is already showing.
return
}
@@ -201,9 +200,7 @@
keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setPrimaryShowingSoon(false)
- repository.setPrimaryVisible(false)
- repository.setPrimaryHide(true)
- repository.setPrimaryShow(null)
+ repository.setPrimaryShow(false)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
Trace.endSection()
}
@@ -320,9 +317,8 @@
val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
val isUnlockingWithFpAllowed: Boolean =
keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
- val bouncerVisible = repository.primaryBouncerVisible.value
val toShow =
- (repository.primaryBouncerVisible.value &&
+ (isBouncerShowing() &&
sfpsEnabled &&
fpsDetectionRunning &&
isUnlockingWithFpAllowed &&
@@ -332,7 +328,7 @@
Log.d(
TAG,
("sideFpsToShow=$toShow\n" +
- "bouncerVisible=$bouncerVisible\n" +
+ "isBouncerShowing=${isBouncerShowing()}\n" +
"configEnabled=$sfpsEnabled\n" +
"fpsDetectionRunning=$fpsDetectionRunning\n" +
"isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
@@ -344,8 +340,7 @@
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
- return (repository.primaryBouncerShowingSoon.value ||
- repository.primaryBouncerVisible.value) &&
+ return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
repository.primaryBouncerStartingDisappearAnimation.value == null
}
@@ -391,6 +386,10 @@
mainHandler.removeCallbacks(showRunnable)
}
+ private fun isBouncerShowing(): Boolean {
+ return repository.primaryBouncerShow.value
+ }
+
companion object {
private const val TAG = "PrimaryBouncerInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index bb617bd..5fcf105 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -27,7 +27,6 @@
import com.android.keyguard.KeyguardSecurityView
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.settingslib.Utils
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
@@ -35,7 +34,6 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ActivityStarter
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -101,6 +99,10 @@
override fun willRunDismissFromKeyguard(): Boolean {
return securityContainerController.willRunDismissFromKeyguard()
}
+
+ override fun showPromptReason(reason: Int) {
+ securityContainerController.showPromptReason(reason)
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -109,26 +111,30 @@
launch {
viewModel.show.collect {
// Reset Security Container entirely.
- securityContainerController.reinflateViewFlipper()
- securityContainerController.showPromptReason(it.promptReason)
- it.errorMessage?.let { errorMessage ->
- securityContainerController.showMessage(
- errorMessage,
- Utils.getColorError(view.context)
+ securityContainerController.reinflateViewFlipper {
+ // Reset Security Container entirely.
+ view.visibility = View.VISIBLE
+ securityContainerController.onBouncerVisibilityChanged(
+ /* isVisible= */ true
)
+ securityContainerController.showPrimarySecurityScreen(
+ /* turningOff= */ false
+ )
+ securityContainerController.appear()
+ securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
}
- securityContainerController.showPrimarySecurityScreen(
- /* turningOff= */ false
- )
- securityContainerController.appear()
- securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
}
}
launch {
viewModel.hide.collect {
+ view.visibility = View.INVISIBLE
+ securityContainerController.onBouncerVisibilityChanged(
+ /* isVisible= */ false
+ )
securityContainerController.cancelDismissAction()
securityContainerController.reset()
+ securityContainerController.onPause()
}
}
@@ -166,19 +172,6 @@
}
launch {
- viewModel.isBouncerVisible.collect { isVisible ->
- view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
- securityContainerController.onBouncerVisibilityChanged(isVisible)
- }
- }
-
- launch {
- viewModel.isBouncerVisible
- .filter { !it }
- .collect { securityContainerController.onPause() }
- }
-
- launch {
viewModel.isInteractable.collect { isInteractable ->
securityContainerController.setInteractable(isInteractable)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 50722d5..6d95882 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -26,18 +26,21 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
@SysUISingleton
class KeyguardRemotePreviewManager
@Inject
constructor(
private val previewRendererFactory: KeyguardPreviewRendererFactory,
+ @Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundHandler: Handler,
) {
@@ -55,7 +58,13 @@
// Destroy any previous renderer associated with this token.
activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
- observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
+ observer =
+ PreviewLifecycleObserver(
+ renderer,
+ applicationScope,
+ mainDispatcher,
+ ::destroyObserver,
+ )
activePreviews[renderer.hostToken] = observer
renderer.render()
renderer.hostToken?.linkToDeath(observer, 0)
@@ -92,13 +101,18 @@
private class PreviewLifecycleObserver(
private val renderer: KeyguardPreviewRenderer,
+ private val scope: CoroutineScope,
private val mainDispatcher: CoroutineDispatcher,
private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
) : Handler.Callback, IBinder.DeathRecipient {
- private var isDestroyed = false
+ private var isDestroyedOrDestroying = false
override fun handleMessage(message: Message): Boolean {
+ if (isDestroyedOrDestroying) {
+ return true
+ }
+
when (message.what) {
KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
message.data
@@ -118,14 +132,14 @@
}
fun onDestroy(): IBinder? {
- if (isDestroyed) {
+ if (isDestroyedOrDestroying) {
return null
}
- isDestroyed = true
+ isDestroyedOrDestroying = true
val hostToken = renderer.hostToken
hostToken?.unlinkToDeath(this, 0)
- runBlocking(mainDispatcher) { renderer.destroy() }
+ scope.launch(mainDispatcher) { renderer.destroy() }
return hostToken
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 97e94d8f3..0656c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
@@ -38,14 +37,11 @@
/** Observe on bouncer expansion amount. */
val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
- /** Observe on bouncer visibility. */
- val isBouncerVisible: Flow<Boolean> = interactor.isVisible
-
/** Can the user interact with the view? */
val isInteractable: Flow<Boolean> = interactor.isInteractable
/** Observe whether bouncer is showing. */
- val show: Flow<KeyguardBouncerModel> = interactor.show
+ val show: Flow<Unit> = interactor.show
/** Observe whether bouncer is hiding. */
val hide: Flow<Unit> = interactor.hide
@@ -74,8 +70,8 @@
/** Observe whether we should update fps is showing. */
val shouldUpdateSideFps: Flow<Unit> =
merge(
- interactor.startingToHide,
- interactor.isVisible.map {},
+ interactor.hide,
+ interactor.show,
interactor.startingDisappearAnimation.filterNotNull().map {}
)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d246b35e..889adc7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -136,6 +136,14 @@
return factory.create("NotifSectionLog", 1000 /* maxSize */, false /* systrace */);
}
+ /** Provides a logging buffer for all logs related to remote input controller. */
+ @Provides
+ @SysUISingleton
+ @NotificationRemoteInputLog
+ public static LogBuffer provideNotificationRemoteInputLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
+ }
+
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
index ad783da..3a639a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,14 +11,15 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.log.dagger
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
- val promptReason: Int = 0,
- val errorMessage: CharSequence? = null,
- val expansionAmount: Float = 0f,
-)
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for NotificationRemoteInput. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class NotificationRemoteInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a31c1e5..00e5aac 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -519,16 +519,15 @@
mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
- // See StatusBarNotificationActivityStarter#onNotificationClicked
boolean showOverLockscreen = mKeyguardStateController.isShowing()
- && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(),
+ && mActivityIntentHelper.wouldPendingShowOverLockscreen(clickIntent,
mLockscreenUserManager.getCurrentUserId());
-
if (showOverLockscreen) {
- mActivityStarter.startActivity(clickIntent.getIntent(),
- /* dismissShade */ true,
- /* animationController */ null,
- /* showOverLockscreenWhenLocked */ true);
+ try {
+ clickIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent for " + key + " was cancelled");
+ }
} else {
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 7fc7bdb..e10d74d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -639,7 +639,9 @@
) =
traceSection("MediaHierarchyManager#updateDesiredLocation") {
val desiredLocation = calculateLocation()
- if (desiredLocation != this.desiredLocation || forceStateUpdate) {
+ if (
+ desiredLocation != this.desiredLocation || forceStateUpdate && !blockLocationChanges
+ ) {
if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
// Only update previous location when it actually changes
previousLocation = this.desiredLocation
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 00e9a79..b71a9193 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,16 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -296,6 +295,8 @@
&& mController.isAdvancedLayoutSupported()) {
//If device is connected and there's other selectable devices, layout as
// one of selected devices.
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
updateGroupableCheckBox(true, isDeviceDeselectable, device);
@@ -371,7 +372,8 @@
mEndClickIcon.setOnClickListener(null);
mEndTouchArea.setOnClickListener(null);
updateEndClickAreaColor(mController.getColorSeekbarProgress());
- mEndClickIcon.setColorFilter(mController.getColorItemContent());
+ mEndClickIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
mEndClickIcon.setOnClickListener(
v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
mEndTouchArea.setOnClickListener(v -> mCheckBox.performClick());
@@ -379,8 +381,8 @@
public void updateEndClickAreaColor(int color) {
if (mController.isAdvancedLayoutSupported()) {
- mEndTouchArea.getBackground().setColorFilter(
- new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+ mEndTouchArea.setBackgroundTintList(
+ ColorStateList.valueOf(color));
}
}
@@ -394,22 +396,22 @@
private void updateConnectionFailedStatusIcon() {
mStatusIcon.setImageDrawable(
mContext.getDrawable(R.drawable.media_output_status_failed));
- mStatusIcon.setColorFilter(mController.getColorItemContent());
+ mStatusIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
}
private void updateDeviceStatusIcon(Drawable drawable) {
mStatusIcon.setImageDrawable(drawable);
- mStatusIcon.setColorFilter(mController.getColorItemContent());
+ mStatusIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
}
private void updateProgressBarColor() {
- mProgressBar.getIndeterminateDrawable().setColorFilter(
- new PorterDuffColorFilter(
- mController.getColorItemContent(),
- PorterDuff.Mode.SRC_IN));
+ mProgressBar.getIndeterminateDrawable().setTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
}
public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) {
@@ -419,9 +421,8 @@
mEndTouchArea.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_YES);
if (mController.isAdvancedLayoutSupported()) {
- mEndTouchArea.getBackground().setColorFilter(
- new PorterDuffColorFilter(mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ mEndTouchArea.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorItemBackground()));
}
setUpContentDescriptionForView(mEndTouchArea, true, device);
}
@@ -450,11 +451,11 @@
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new));
final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
mTitleIcon.setImageDrawable(addDrawable);
- mTitleIcon.setColorFilter(mController.getColorItemContent());
+ mTitleIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.getBackground().setColorFilter(
- new PorterDuffColorFilter(mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorItemBackground()));
}
mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
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 2a2cf63..f76f049 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -23,8 +23,7 @@
import android.annotation.DrawableRes;
import android.app.WallpaperColors;
import android.content.Context;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
@@ -196,9 +195,8 @@
mIconAreaLayout.setOnClickListener(null);
mVolumeValueText.setTextColor(mController.getColorItemContent());
}
- mSeekBar.getProgressDrawable().setColorFilter(
- new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
- PorterDuff.Mode.SRC_IN));
+ mSeekBar.setProgressTintList(
+ ColorStateList.valueOf(mController.getColorSeekbarProgress()));
}
abstract void onBind(int customizedItem);
@@ -224,16 +222,14 @@
updateSeekbarProgressBackground();
}
}
- mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
- isActive ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ mItemLayout.setBackgroundTintList(
+ ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground()));
if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
- showSeekBar ? mController.getColorSeekbarProgress()
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress()
: showProgressBar ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ : mController.getColorItemBackground()));
}
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
@@ -251,7 +247,8 @@
params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
: mController.getItemMarginEndDefault();
}
- mTitleIcon.setColorFilter(mController.getColorItemContent());
+ mTitleIcon.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -274,15 +271,14 @@
backgroundDrawable = mContext.getDrawable(
showSeekBar ? R.drawable.media_output_item_background_active
: R.drawable.media_output_item_background).mutate();
- backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+ backgroundDrawable.setTint(
showSeekBar ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
- mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
- showProgressBar || isFakeActive
+ : mController.getColorItemBackground());
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(showProgressBar || isFakeActive
? mController.getColorConnectedItemBackground()
: showSeekBar ? mController.getColorSeekbarProgress()
- : mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ : mController.getColorItemBackground()));
if (showSeekBar) {
updateSeekbarProgressBackground();
}
@@ -297,9 +293,7 @@
backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background)
.mutate();
- backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
- mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ backgroundDrawable.setTint(mController.getColorItemBackground());
}
mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
@@ -442,11 +436,10 @@
void updateTitleIcon(@DrawableRes int id, int color) {
mTitleIcon.setImageDrawable(mContext.getDrawable(id));
- mTitleIcon.setColorFilter(color);
+ mTitleIcon.setImageTintList(ColorStateList.valueOf(color));
if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.getBackground().setColorFilter(
- new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
- PorterDuff.Mode.SRC_IN));
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorSeekbarProgress()));
}
}
@@ -462,9 +455,7 @@
final Drawable backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background_active)
.mutate();
- backgroundDrawable.setColorFilter(
- new PorterDuffColorFilter(mController.getColorConnectedItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ backgroundDrawable.setTint(mController.getColorConnectedItemBackground());
mItemLayout.setBackground(backgroundDrawable);
}
@@ -539,10 +530,8 @@
Drawable getSpeakerDrawable() {
final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
.mutate();
- drawable.setColorFilter(
- new PorterDuffColorFilter(Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_item_main_content),
- PorterDuff.Mode.SRC_IN));
+ drawable.setTint(Utils.getColorStateListDefaultColor(mContext,
+ R.color.media_dialog_item_main_content));
return drawable;
}
@@ -574,7 +563,9 @@
return;
}
mTitleIcon.setImageIcon(icon);
- mTitleIcon.setColorFilter(mController.getColorItemContent());
+ icon.setTint(mController.getColorItemContent());
+ mTitleIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
});
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 2250d72..39d4e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -80,6 +80,10 @@
Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
}
+ if (mSourceDevice == null && mTargetDevice == null) {
+ return;
+ }
+
updateLoggingDeviceCount(deviceList);
SysUiStatsLog.write(
@@ -105,6 +109,10 @@
Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
}
+ if (mSourceDevice == null && mTargetDevice == null) {
+ return;
+ }
+
updateLoggingMediaItemCount(deviceItemList);
SysUiStatsLog.write(
@@ -176,6 +184,10 @@
Log.e(TAG, "logRequestFailed - " + reason);
}
+ if (mSourceDevice == null && mTargetDevice == null) {
+ return;
+ }
+
updateLoggingDeviceCount(deviceList);
SysUiStatsLog.write(
@@ -201,6 +213,10 @@
Log.e(TAG, "logRequestFailed - " + reason);
}
+ if (mSourceDevice == null && mTargetDevice == null) {
+ return;
+ }
+
updateLoggingMediaItemCount(deviceItemList);
SysUiStatsLog.write(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index fab8c06..78082c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -225,8 +225,10 @@
val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
val translationYBy = getTranslationAmount()
+ // Expand ripple before translating icon container to make sure both views have same bounds.
+ rippleController.expandToInProgressState(rippleView, iconRippleView)
// Make the icon container view starts animation from bottom of the screen.
- iconContainerView.translationY += rippleController.getReceiverIconSize()
+ iconContainerView.translationY = rippleController.getReceiverIconSize().toFloat()
animateViewTranslationAndFade(
iconContainerView,
translationYBy = -1 * translationYBy,
@@ -235,7 +237,6 @@
) {
animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
}
- rippleController.expandToInProgressState(rippleView, iconRippleView)
}
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
@@ -293,7 +294,7 @@
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Float {
- return rippleController.getRippleSize() * 0.5f
+ return rippleController.getReceiverIconSize() * 2f
}
private fun View.getAppIconView(): CachingIconView {
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
new file mode 100644
index 0000000..c48028c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.model
+
+import com.android.systemui.multishade.shared.model.ShadeId
+
+/** Models the current interaction with one of the shades. */
+data class MultiShadeInteractionModel(
+ /** The ID of the shade that the user is currently interacting with. */
+ val shadeId: ShadeId,
+ /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
+ val isProxied: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
new file mode 100644
index 0000000..86f0c0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.remoteproxy
+
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+/**
+ * Acts as a hub for routing proxied user input into the multi shade system.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself. This class
+ * is that proxy.
+ */
+@Singleton
+class MultiShadeInputProxy @Inject constructor() {
+ private val _proxiedTouch =
+ MutableSharedFlow<ProxiedInputModel>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
+
+ fun onProxiedInput(proxiedInput: ProxiedInputModel) {
+ _proxiedTouch.tryEmit(proxiedInput)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
new file mode 100644
index 0000000..1172030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.repository
+
+import android.content.Context
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates application state for all shades. */
+@SysUISingleton
+class MultiShadeRepository
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ inputProxy: MultiShadeInputProxy,
+) {
+ /**
+ * Remote input coming from sources outside of system UI (for example, swiping down on the
+ * Launcher or from the status bar).
+ */
+ val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
+
+ /** Width of the left-hand side shade, in pixels. */
+ private val leftShadeWidthPx =
+ applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
+
+ /** Width of the right-hand side shade, in pixels. */
+ private val rightShadeWidthPx =
+ applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
+
+ /**
+ * The amount that the user must swipe up when the shade is fully expanded to automatically
+ * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully expanded once the user stops swiping.
+ *
+ * This is a fraction between `0` and `1`.
+ */
+ private val swipeCollapseThreshold =
+ checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
+
+ /**
+ * The amount that the user must swipe down when the shade is fully collapsed to automatically
+ * expand once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully collapsed once the user stops swiping.
+ *
+ * This is a fraction between `0` and `1`.
+ */
+ private val swipeExpandThreshold =
+ checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
+
+ /**
+ * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
+ *
+ * This is a fraction between `0` and `1`.
+ */
+ private val dualShadeScrimAlpha =
+ checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
+
+ /** The current configuration of the shade system. */
+ val shadeConfig: StateFlow<ShadeConfig> =
+ MutableStateFlow(
+ if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
+ ShadeConfig.DualShadeConfig(
+ leftShadeWidthPx = leftShadeWidthPx,
+ rightShadeWidthPx = rightShadeWidthPx,
+ swipeCollapseThreshold = swipeCollapseThreshold,
+ swipeExpandThreshold = swipeExpandThreshold,
+ splitFraction =
+ applicationContext.resources.getFloat(
+ R.dimen.dual_shade_split_fraction
+ ),
+ scrimAlpha = dualShadeScrimAlpha,
+ )
+ } else {
+ ShadeConfig.SingleShadeConfig(
+ swipeCollapseThreshold = swipeCollapseThreshold,
+ swipeExpandThreshold = swipeExpandThreshold,
+ )
+ }
+ )
+ .asStateFlow()
+
+ private val _forceCollapseAll = MutableStateFlow(false)
+ /** Whether all shades should be collapsed. */
+ val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
+
+ private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
+ /** The current shade interaction or `null` if no shade is interacted with currently. */
+ val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
+
+ private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
+
+ /** The model for the shade with the given ID. */
+ fun getShade(
+ shadeId: ShadeId,
+ ): StateFlow<ShadeModel> {
+ return getMutableShade(shadeId).asStateFlow()
+ }
+
+ /** Sets the expansion amount for the shade with the given ID. */
+ fun setExpansion(
+ shadeId: ShadeId,
+ @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+ ) {
+ getMutableShade(shadeId).let { mutableState ->
+ mutableState.value = mutableState.value.copy(expansion = expansion)
+ }
+ }
+
+ /** Sets whether all shades should be immediately forced to collapse. */
+ fun setForceCollapseAll(isForced: Boolean) {
+ _forceCollapseAll.value = isForced
+ }
+
+ /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
+ fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
+ _shadeInteraction.value = shadeInteraction
+ }
+
+ private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
+ return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
+ }
+
+ /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
+ private fun checkInBounds(float: Float): Float {
+ check(float in 0f..1f) { "$float isn't between 0 and 1." }
+ return float
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
new file mode 100644
index 0000000..b9f6d83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.domain.interactor
+
+import androidx.annotation.FloatRange
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.yield
+
+/** Encapsulates business logic related to interactions with the multi-shade system. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MultiShadeInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val repository: MultiShadeRepository,
+ private val inputProxy: MultiShadeInputProxy,
+) {
+ /** The current configuration of the shade system. */
+ val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
+
+ /** The expansion of the shade that's most expanded. */
+ val maxShadeExpansion: Flow<Float> =
+ repository.shadeConfig.flatMapLatest { shadeConfig ->
+ combine(allShades(shadeConfig)) { shadeModels ->
+ shadeModels.maxOfOrNull { it.expansion } ?: 0f
+ }
+ }
+
+ /**
+ * A _processed_ version of the proxied input flow.
+ *
+ * All internal dependencies on the proxied input flow *must* use this one for two reasons:
+ * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
+ * actually have.
+ * 2. It actually does some preprocessing as the proxied input events stream through, handling
+ * common things like recording the current state of the system based on incoming input
+ * events.
+ */
+ private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
+ combine(
+ repository.shadeConfig,
+ repository.proxiedInput.distinctUntilChanged(),
+ ::Pair,
+ )
+ .map { (shadeConfig, proxiedInput) ->
+ if (proxiedInput !is ProxiedInputModel.OnTap) {
+ // If the user is interacting with any other gesture type (for instance,
+ // dragging),
+ // we no longer want to force collapse all shades.
+ repository.setForceCollapseAll(false)
+ }
+
+ when (proxiedInput) {
+ is ProxiedInputModel.OnDrag -> {
+ val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
+ // This might be the start of a new drag gesture, let's update our
+ // application
+ // state to record that fact.
+ onUserInteractionStarted(
+ shadeId = affectedShadeId,
+ isProxied = true,
+ )
+ }
+ is ProxiedInputModel.OnTap -> {
+ // Tapping outside any shade collapses all shades. This code path is not hit
+ // for
+ // taps that happen _inside_ a shade as that input event is directly applied
+ // through the UI and is, hence, not a proxied input.
+ collapseAll()
+ }
+ else -> Unit
+ }
+
+ proxiedInput
+ }
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ replay = 1,
+ )
+
+ /** Whether the shade with the given ID should be visible. */
+ fun isVisible(shadeId: ShadeId): Flow<Boolean> {
+ return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
+ }
+
+ /** Whether direct user input is allowed on the shade with the given ID. */
+ fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
+ return combine(
+ isForceCollapsed(shadeId),
+ repository.shadeInteraction,
+ ::Pair,
+ )
+ .map { (isForceCollapsed, shadeInteraction) ->
+ !isForceCollapsed && shadeInteraction?.isProxied != true
+ }
+ }
+
+ /** Whether the shade with the given ID is forced to collapse. */
+ fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
+ return combine(
+ repository.forceCollapseAll,
+ repository.shadeInteraction.map { it?.shadeId },
+ ::Pair,
+ )
+ .map { (collapseAll, userInteractedShadeIdOrNull) ->
+ val counterpartShadeIdOrNull =
+ when (shadeId) {
+ ShadeId.SINGLE -> null
+ ShadeId.LEFT -> ShadeId.RIGHT
+ ShadeId.RIGHT -> ShadeId.LEFT
+ }
+
+ when {
+ // If all shades have been told to collapse (by a tap outside, for example),
+ // then this shade is collapsed.
+ collapseAll -> true
+ // A shade that doesn't have a counterpart shade cannot be force-collapsed by
+ // interactions on the counterpart shade.
+ counterpartShadeIdOrNull == null -> false
+ // If the current user interaction is on the counterpart shade, then this shade
+ // should be force-collapsed.
+ else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
+ }
+ }
+ }
+
+ /**
+ * Proxied input affecting the shade with the given ID. This is input coming from sources
+ * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
+ * outside the UI of any shade (for example, the scrim that's shown behind the shades).
+ */
+ fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
+ return combine(
+ processedProxiedInput,
+ isForceCollapsed(shadeId).distinctUntilChanged(),
+ repository.shadeInteraction,
+ ::Triple,
+ )
+ .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
+ when {
+ // If the shade is force-collapsed, we ignored proxied input on it.
+ isForceCollapsed -> null
+ // If the proxied input does not belong to this shade, ignore it.
+ shadeInteraction?.shadeId != shadeId -> null
+ // If there is ongoing non-proxied user input on any shade, ignore the
+ // proxied input.
+ !shadeInteraction.isProxied -> null
+ // Otherwise, send the proxied input downstream.
+ else -> proxiedInput
+ }
+ }
+ .onEach { proxiedInput ->
+ // We use yield() to make sure that the following block of code happens _after_
+ // downstream collectors had a chance to process the proxied input. Otherwise, we
+ // might change our state to clear the current UserInteraction _before_ those
+ // downstream collectors get a chance to process the proxied input, which will make
+ // them ignore it (since they ignore proxied input when the current user interaction
+ // doesn't match their shade).
+ yield()
+
+ if (
+ proxiedInput is ProxiedInputModel.OnDragEnd ||
+ proxiedInput is ProxiedInputModel.OnDragCancel
+ ) {
+ onUserInteractionEnded(shadeId = shadeId, isProxied = true)
+ }
+ }
+ }
+
+ /** Sets the expansion amount for the shade with the given ID. */
+ fun setExpansion(
+ shadeId: ShadeId,
+ @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+ ) {
+ repository.setExpansion(shadeId, expansion)
+ }
+
+ /** Collapses all shades. */
+ fun collapseAll() {
+ repository.setForceCollapseAll(true)
+ }
+
+ /**
+ * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
+ * the same interaction as it won't overwrite an existing interaction.
+ *
+ * Existing interactions can be cleared by calling [onUserInteractionEnded].
+ */
+ fun onUserInteractionStarted(shadeId: ShadeId) {
+ onUserInteractionStarted(
+ shadeId = shadeId,
+ isProxied = false,
+ )
+ }
+
+ /**
+ * Notifies that the current non-proxied interaction has ended.
+ *
+ * Safe to call multiple times, even if there's no current interaction or even if the current
+ * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
+ * there's a match between the parameters and the current interaction.
+ */
+ fun onUserInteractionEnded(
+ shadeId: ShadeId,
+ ) {
+ onUserInteractionEnded(
+ shadeId = shadeId,
+ isProxied = false,
+ )
+ }
+
+ fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
+ inputProxy.onProxiedInput(proxiedInput)
+ }
+
+ /**
+ * Notifies that a new interaction may have started. Safe to call multiple times for the same
+ * interaction as it won't overwrite an existing interaction.
+ *
+ * Existing interactions can be cleared by calling [onUserInteractionEnded].
+ */
+ private fun onUserInteractionStarted(
+ shadeId: ShadeId,
+ isProxied: Boolean,
+ ) {
+ if (repository.shadeInteraction.value != null) {
+ return
+ }
+
+ repository.setShadeInteraction(
+ MultiShadeInteractionModel(
+ shadeId = shadeId,
+ isProxied = isProxied,
+ )
+ )
+ }
+
+ /**
+ * Notifies that the current interaction has ended.
+ *
+ * Safe to call multiple times, even if there's no current interaction or even if the current
+ * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
+ * unless there's a match between the parameters and the current interaction.
+ */
+ private fun onUserInteractionEnded(
+ shadeId: ShadeId,
+ isProxied: Boolean,
+ ) {
+ repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
+ if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
+ repository.setShadeInteraction(null)
+ }
+ }
+ }
+
+ /**
+ * Returns the ID of the shade that's affected by user input at a given coordinate.
+ *
+ * @param config The shade configuration being used.
+ * @param xFraction The horizontal position of the user input as a fraction along the width of
+ * its container where `0` is all the way to the left and `1` is all the way to the right.
+ */
+ private fun affectedShadeId(
+ config: ShadeConfig,
+ @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
+ ): ShadeId {
+ return if (config is ShadeConfig.DualShadeConfig) {
+ if (xFraction <= config.splitFraction) {
+ ShadeId.LEFT
+ } else {
+ ShadeId.RIGHT
+ }
+ } else {
+ ShadeId.SINGLE
+ }
+ }
+
+ /** Returns the list of flows of all the shades in the given configuration. */
+ private fun allShades(
+ config: ShadeConfig,
+ ): List<Flow<ShadeModel>> {
+ return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
new file mode 100644
index 0000000..ee1dd65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/**
+ * Models a part of an ongoing proxied user input gesture.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself.
+ */
+sealed class ProxiedInputModel {
+ /** The user is dragging their pointer. */
+ data class OnDrag(
+ /**
+ * The relative position of the pointer as a fraction of its container width where `0` is
+ * all the way to the left and `1` is all the way to the right.
+ */
+ @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
+ /** The amount that the pointer was dragged, in pixels. */
+ val yDragAmountPx: Float,
+ ) : ProxiedInputModel()
+
+ /** The user finished dragging by lifting up their pointer. */
+ object OnDragEnd : ProxiedInputModel()
+
+ /**
+ * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
+ */
+ object OnDragCancel : ProxiedInputModel()
+
+ /** The user has tapped (clicked). */
+ object OnTap : ProxiedInputModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
new file mode 100644
index 0000000..a4cd35c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Enumerates the various possible configurations of the shade system. */
+sealed class ShadeConfig(
+
+ /** IDs of the shade(s) in this configuration. */
+ open val shadeIds: List<ShadeId>,
+
+ /**
+ * The amount that the user must swipe up when the shade is fully expanded to automatically
+ * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully expanded once the user stops swiping.
+ */
+ @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
+
+ /**
+ * The amount that the user must swipe down when the shade is fully collapsed to automatically
+ * expand once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully collapsed once the user stops swiping.
+ */
+ @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
+) {
+
+ /** There is a single shade. */
+ data class SingleShadeConfig(
+ @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+ @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+ ) :
+ ShadeConfig(
+ shadeIds = listOf(ShadeId.SINGLE),
+ swipeCollapseThreshold = swipeCollapseThreshold,
+ swipeExpandThreshold = swipeExpandThreshold,
+ )
+
+ /** There are two shades arranged side-by-side. */
+ data class DualShadeConfig(
+ /** Width of the left-hand side shade. */
+ val leftShadeWidthPx: Int,
+ /** Width of the right-hand side shade. */
+ val rightShadeWidthPx: Int,
+ @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+ @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+ /**
+ * The position of the "split" between interaction areas for each of the shades, as a
+ * fraction of the width of the container.
+ *
+ * Interactions that occur on the start-side (left-hand side in left-to-right languages like
+ * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
+ * side in left-to-right languages like English) affect the end-side shade.
+ */
+ @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
+ /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
+ @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
+ ) :
+ ShadeConfig(
+ shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
+ swipeCollapseThreshold = swipeCollapseThreshold,
+ swipeExpandThreshold = swipeExpandThreshold,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
new file mode 100644
index 0000000..9e02657
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+/** Enumerates all known shade IDs. */
+enum class ShadeId {
+ /** ID of the shade on the left in dual shade configurations. */
+ LEFT,
+ /** ID of the shade on the right in dual shade configurations. */
+ RIGHT,
+ /** ID of the single shade in single shade configurations. */
+ SINGLE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
rename to packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
index ad783da..49ac64c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,14 +11,16 @@
* 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
+ * limitations under the License.
+ *
*/
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.multishade.shared.model
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
- val promptReason: Int = 0,
- val errorMessage: CharSequence? = null,
- val expansionAmount: Float = 0f,
+import androidx.annotation.FloatRange
+
+/** Models the current state of a shade. */
+data class ShadeModel(
+ val id: ShadeId,
+ @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
new file mode 100644
index 0000000..aecec39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.launch
+
+/**
+ * View that hosts the multi-shade system and acts as glue between legacy code and the
+ * implementation.
+ */
+class MultiShadeView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ FrameLayout(
+ context,
+ attrs,
+ ) {
+
+ fun init(
+ interactor: MultiShadeInteractor,
+ clock: SystemClock,
+ ) {
+ repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ addView(
+ ComposeFacade.createMultiShadeView(
+ context = context,
+ viewModel =
+ MultiShadeViewModel(
+ viewModelScope = this,
+ interactor = interactor,
+ ),
+ clock = clock,
+ )
+ )
+ }
+
+ // Here when destroyed.
+ removeAllViews()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
new file mode 100644
index 0000000..ce6ab97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for UI that supports multi (or single) shade. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MultiShadeViewModel(
+ viewModelScope: CoroutineScope,
+ private val interactor: MultiShadeInteractor,
+) {
+ /** Models UI state for the single shade. */
+ val singleShade =
+ ShadeViewModel(
+ viewModelScope,
+ ShadeId.SINGLE,
+ interactor,
+ )
+
+ /** Models UI state for the shade on the left-hand side. */
+ val leftShade =
+ ShadeViewModel(
+ viewModelScope,
+ ShadeId.LEFT,
+ interactor,
+ )
+
+ /** Models UI state for the shade on the right-hand side. */
+ val rightShade =
+ ShadeViewModel(
+ viewModelScope,
+ ShadeId.RIGHT,
+ interactor,
+ )
+
+ /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
+ val scrimAlpha: StateFlow<Float> =
+ combine(
+ interactor.maxShadeExpansion,
+ interactor.shadeConfig
+ .map { it as? ShadeConfig.DualShadeConfig }
+ .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
+ ::Pair,
+ )
+ .map { (anyShadeExpansion, scrimAlpha) ->
+ (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
+ }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 0f,
+ )
+
+ /** Whether the scrim should accept touch events. */
+ val isScrimEnabled: StateFlow<Boolean> =
+ interactor.shadeConfig
+ .flatMapLatest { shadeConfig ->
+ when (shadeConfig) {
+ // In the dual shade configuration, the scrim is enabled when the expansion is
+ // greater than zero on any one of the shades.
+ is ShadeConfig.DualShadeConfig ->
+ interactor.maxShadeExpansion
+ .map { expansion -> expansion > 0 }
+ .distinctUntilChanged()
+ // No scrim in the single shade configuration.
+ is ShadeConfig.SingleShadeConfig -> flowOf(false)
+ }
+ }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Notifies that the scrim has been touched. */
+ fun onScrimTouched(proxiedInput: ProxiedInputModel) {
+ if (!isScrimEnabled.value) {
+ return
+ }
+
+ interactor.sendProxiedInput(proxiedInput)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
new file mode 100644
index 0000000..e828dbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.annotation.FloatRange
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for a single shade. */
+class ShadeViewModel(
+ viewModelScope: CoroutineScope,
+ private val shadeId: ShadeId,
+ private val interactor: MultiShadeInteractor,
+) {
+ /** Whether the shade is visible. */
+ val isVisible: StateFlow<Boolean> =
+ interactor
+ .isVisible(shadeId)
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Whether swiping on the shade UI is currently enabled. */
+ val isSwipingEnabled: StateFlow<Boolean> =
+ interactor
+ .isNonProxiedInputAllowed(shadeId)
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Whether the shade must be collapsed immediately. */
+ val isForceCollapsed: Flow<Boolean> =
+ interactor.isForceCollapsed(shadeId).distinctUntilChanged()
+
+ /** The width of the shade. */
+ val width: StateFlow<Size> =
+ interactor.shadeConfig
+ .map { shadeWidth(it) }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = shadeWidth(interactor.shadeConfig.value),
+ )
+
+ /**
+ * The amount that the user must swipe up when the shade is fully expanded to automatically
+ * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully expanded once the user stops swiping.
+ */
+ val swipeCollapseThreshold: StateFlow<Float> =
+ interactor.shadeConfig
+ .map { it.swipeCollapseThreshold }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
+ )
+
+ /**
+ * The amount that the user must swipe down when the shade is fully collapsed to automatically
+ * expand once the user lets go of the shade. If the user swipes less than this amount, the
+ * shade will automatically revert back to fully collapsed once the user stops swiping.
+ */
+ val swipeExpandThreshold: StateFlow<Float> =
+ interactor.shadeConfig
+ .map { it.swipeExpandThreshold }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
+ )
+
+ /**
+ * Proxied input affecting the shade. This is input coming from sources outside of system UI
+ * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
+ * shade (for example, the scrim that's shown behind the shades).
+ */
+ val proxiedInput: Flow<ProxiedInputModel?> =
+ interactor
+ .proxiedInput(shadeId)
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
+ /** Notifies that the expansion amount for the shade has changed. */
+ fun onExpansionChanged(
+ expansion: Float,
+ ) {
+ interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
+ }
+
+ /** Notifies that a drag gesture has started. */
+ fun onDragStarted() {
+ interactor.onUserInteractionStarted(shadeId)
+ }
+
+ /** Notifies that a drag gesture has ended. */
+ fun onDragEnded() {
+ interactor.onUserInteractionEnded(shadeId = shadeId)
+ }
+
+ private fun shadeWidth(shadeConfig: ShadeConfig): Size {
+ return when (shadeId) {
+ ShadeId.LEFT ->
+ Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
+ ShadeId.RIGHT ->
+ Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
+ ShadeId.SINGLE -> Size.Fraction(1f)
+ }
+ }
+
+ sealed class Size {
+ data class Fraction(
+ @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
+ ) : Size()
+ data class Pixels(
+ val pixels: Int,
+ ) : Size()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ac22b7c..779f1d8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -129,8 +129,7 @@
logDebug { "onShowNoteTask - start: $info" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
- // TODO(b/267634412, b/268351693): Should use `showOrHideAppBubbleAsUser`
- bubbles.showOrHideAppBubble(intent)
+ bubbles.showOrHideAppBubble(intent, userTracker.userHandle)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 8a3ecc6..0748bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
-
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -713,6 +712,7 @@
}
public void startConnectionToCurrentUser() {
+ Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted");
if (mHandler.getLooper() != Looper.myLooper()) {
mHandler.post(mConnectionRunnable);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ca8e101..02a60ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -481,7 +481,6 @@
mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(),
extraPadding + mPreview.getPaddingBottom());
imageTop += (previewHeight - imageHeight) / 2;
- mCropView.setExtraPadding(extraPadding, extraPadding);
mCropView.setImageWidth(previewWidth);
scale = previewWidth / (float) mPreview.getDrawable().getIntrinsicWidth();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e2f31e8..a716a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,13 +16,13 @@
package com.android.systemui.shade;
+import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_BACK;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputDevice;
@@ -30,6 +30,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewStub;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
@@ -38,7 +39,10 @@
import com.android.systemui.R;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -47,6 +51,8 @@
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
+import com.android.systemui.multishade.ui.view.MultiShadeView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -61,11 +67,13 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationShadeWindowView}.
@@ -88,10 +96,12 @@
private final NotificationInsetsController mNotificationInsetsController;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+ private final boolean mIsTrackpadGestureBackEnabled;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
+ private MotionEvent mDownEvent;
private boolean mExpandAnimationRunning;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarViewController mStatusBarViewController;
@@ -111,6 +121,7 @@
mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
+ private final SystemClock mClock;
@Inject
public NotificationShadeWindowViewController(
@@ -137,8 +148,10 @@
AlternateBouncerInteractor alternateBouncerInteractor,
UdfpsOverlayInteractor udfpsOverlayInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
- ) {
+ PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+ FeatureFlags featureFlags,
+ Provider<MultiShadeInteractor> multiShadeInteractorProvider,
+ SystemClock clock) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -159,6 +172,7 @@
mNotificationInsetsController = notificationInsetsController;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+ mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_BACK);
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -170,6 +184,16 @@
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
+
+ mClock = clock;
+ if (ComposeFacade.INSTANCE.isComposeAvailable()
+ && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+ final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
+ if (multiShadeViewStub != null) {
+ final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
+ multiShadeView.init(multiShadeInteractorProvider.get(), clock);
+ }
+ }
}
/**
@@ -219,9 +243,11 @@
if (isDown) {
mTouchActive = true;
mTouchCancelled = false;
+ mDownEvent = ev;
} else if (ev.getActionMasked() == MotionEvent.ACTION_UP
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
mTouchActive = false;
+ mDownEvent = null;
}
if (mTouchCancelled || mExpandAnimationRunning) {
return false;
@@ -262,7 +288,7 @@
mLockIconViewController.onTouchEvent(
ev,
() -> mService.wakeUpIfDozing(
- SystemClock.uptimeMillis(),
+ mClock.uptimeMillis(),
mView,
"LOCK_ICON_TOUCH",
PowerManager.WAKE_REASON_GESTURE)
@@ -446,10 +472,18 @@
public void cancelCurrentTouch() {
if (mTouchActive) {
- final long now = SystemClock.uptimeMillis();
- MotionEvent event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ final long now = mClock.uptimeMillis();
+ final MotionEvent event;
+ if (mIsTrackpadGestureBackEnabled) {
+ event = MotionEvent.obtain(mDownEvent);
+ event.setDownTime(now);
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ event.setLocation(0.0f, 0.0f);
+ } else {
+ event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ }
mView.dispatchTouchEvent(event);
event.recycle();
mTouchCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 5736a5c..26149321 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -37,7 +37,8 @@
fun create(
@BindsInstance parent: ViewGroup,
@BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
- @BindsInstance onAttachListener: View.OnAttachStateChangeListener
+ @BindsInstance onAttachListener: View.OnAttachStateChangeListener,
+ @BindsInstance viewWithCustomLayout: View? = null
): SmartspaceViewComponent
}
@@ -53,10 +54,13 @@
falsingManager: FalsingManager,
parent: ViewGroup,
@Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
+ viewWithCustomLayout: View?,
onAttachListener: View.OnAttachStateChangeListener
):
BcSmartspaceDataPlugin.SmartspaceView {
- val ssView = plugin.getView(parent)
+ val ssView = viewWithCustomLayout
+ as? BcSmartspaceDataPlugin.SmartspaceView
+ ?: plugin.getView(parent)
// Currently, this is only used to provide SmartspaceView on Dream surface.
ssView.setUiSurface(UI_SURFACE_DREAM)
ssView.registerDataProvider(plugin)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 01e042b..c920e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -657,25 +657,6 @@
R.string.input_switch_input_language_previous),
KeyEvent.KEYCODE_SPACE,
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON),
- null))),
- /* Access emoji: Meta + . */
- new ShortcutMultiMappingInfo(
- context.getString(R.string.input_access_emoji),
- null,
- Arrays.asList(
- new ShortcutKeyGroup(new KeyboardShortcutInfo(
- context.getString(R.string.input_access_emoji),
- KeyEvent.KEYCODE_PERIOD,
- KeyEvent.META_META_ON),
- null))),
- /* Access voice typing: Meta + V */
- new ShortcutMultiMappingInfo(
- context.getString(R.string.input_access_voice_typing),
- null,
- Arrays.asList(
- new ShortcutKeyGroup(new KeyboardShortcutInfo(
- context.getString(R.string.input_access_voice_typing),
- KeyEvent.KEYCODE_V, KeyEvent.META_META_ON),
null)))
);
return new KeyboardShortcutMultiMappingGroup(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 51c5183..cac4251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.KeyguardManager;
@@ -145,7 +144,10 @@
break;
case Intent.ACTION_USER_UNLOCKED:
// Start the overview connection to the launcher service
- mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ // Connect if user hasn't connected yet
+ if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ }
break;
case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
final IntentSender intentSender = intent.getParcelableExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 99081e9..9e2a07e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -65,6 +66,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -72,8 +75,6 @@
import java.util.Optional;
import java.util.function.Consumer;
-import dagger.Lazy;
-
/**
* Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -104,6 +105,8 @@
private final KeyguardManager mKeyguardManager;
private final StatusBarStateController mStatusBarStateController;
private final RemoteInputUriController mRemoteInputUriController;
+
+ private final RemoteInputControllerLogger mRemoteInputControllerLogger;
private final NotificationClickNotifier mClickNotifier;
protected RemoteInputController mRemoteInputController;
@@ -259,6 +262,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
StatusBarStateController statusBarStateController,
RemoteInputUriController remoteInputUriController,
+ RemoteInputControllerLogger remoteInputControllerLogger,
NotificationClickNotifier clickNotifier,
ActionClickLogger logger,
DumpManager dumpManager) {
@@ -275,6 +279,7 @@
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
+ mRemoteInputControllerLogger = remoteInputControllerLogger;
mClickNotifier = clickNotifier;
dumpManager.registerDumpable(this);
@@ -294,7 +299,8 @@
/** Initializes this component with the provided dependencies. */
public void setUpWithCallback(Callback callback, RemoteInputController.Delegate delegate) {
mCallback = callback;
- mRemoteInputController = new RemoteInputController(delegate, mRemoteInputUriController);
+ mRemoteInputController = new RemoteInputController(delegate,
+ mRemoteInputUriController, mRemoteInputControllerLogger);
if (mRemoteInputListener != null) {
mRemoteInputListener.setRemoteInputController(mRemoteInputController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index f44f598..a37b2a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -52,10 +53,14 @@
private final Delegate mDelegate;
private final RemoteInputUriController mRemoteInputUriController;
+ private final RemoteInputControllerLogger mLogger;
+
public RemoteInputController(Delegate delegate,
- RemoteInputUriController remoteInputUriController) {
+ RemoteInputUriController remoteInputUriController,
+ RemoteInputControllerLogger logger) {
mDelegate = delegate;
mRemoteInputUriController = remoteInputUriController;
+ mLogger = logger;
}
/**
@@ -117,6 +122,9 @@
boolean isActive = isRemoteInputActive(entry);
boolean found = pruneWeakThenRemoveAndContains(
entry /* contains */, null /* remove */, token /* removeToken */);
+ mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
+ isActive /* isRemoteInputAlreadyActive */,
+ found /* isRemoteInputFound */);
if (!found) {
mOpen.add(new Pair<>(new WeakReference<>(entry), token));
}
@@ -137,9 +145,22 @@
*/
public void removeRemoteInput(NotificationEntry entry, Object token) {
Objects.requireNonNull(entry);
- if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) return;
+ if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
+ mLogger.logRemoveRemoteInput(
+ entry.getKey() /* entryKey*/,
+ true /* remoteEditImeVisible */,
+ true /* remoteEditImeAnimatingAway */);
+ return;
+ }
// If the view is being removed, this may be called even though we're not active
- if (!isRemoteInputActive(entry)) return;
+ boolean remoteInputActive = isRemoteInputActive(entry);
+ mLogger.logRemoveRemoteInput(
+ entry.getKey() /* entryKey*/,
+ entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
+ entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
+ remoteInputActive /* isRemoteInputActive */);
+
+ if (!remoteInputActive) return;
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index b9ac918..79d01b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -56,6 +56,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.util.Compile;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -299,7 +300,7 @@
@Override
public boolean setIsDreaming(boolean isDreaming) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG) {
Log.d(TAG, "setIsDreaming:" + isDreaming);
}
if (mIsDreaming == isDreaming) {
@@ -321,6 +322,11 @@
}
@Override
+ public boolean isDreaming() {
+ return mIsDreaming;
+ }
+
+ @Override
public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
if (animated && mDozeAmountTarget == dozeAmount) {
@@ -580,6 +586,7 @@
pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide);
pw.println(" mKeyguardRequested=" + mKeyguardRequested);
pw.println(" mIsDozing=" + mIsDozing);
+ pw.println(" mIsDreaming=" + mIsDreaming);
pw.println(" mListeners{" + mListeners.size() + "}=");
for (RankedListener rl : mListeners) {
pw.println(" " + rl.mListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index d7568a9..565c0a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -77,14 +78,14 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
/**
* This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
* this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -105,6 +106,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
StatusBarStateController statusBarStateController,
RemoteInputUriController remoteInputUriController,
+ RemoteInputControllerLogger remoteInputControllerLogger,
NotificationClickNotifier clickNotifier,
ActionClickLogger actionClickLogger,
DumpManager dumpManager) {
@@ -117,6 +119,7 @@
centralSurfacesOptionalLazy,
statusBarStateController,
remoteInputUriController,
+ remoteInputControllerLogger,
clickNotifier,
actionClickLogger,
dumpManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
new file mode 100644
index 0000000..9582dfad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.NotificationRemoteInputLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import javax.inject.Inject
+
+/** Logger class for [RemoteInputController]. */
+@SysUISingleton
+class RemoteInputControllerLogger
+@Inject
+constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
+
+ /** logs addRemoteInput invocation of [RemoteInputController] */
+ fun logAddRemoteInput(
+ entryKey: String,
+ isRemoteInputAlreadyActive: Boolean,
+ isRemoteInputFound: Boolean
+ ) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = entryKey
+ bool1 = isRemoteInputAlreadyActive
+ bool2 = isRemoteInputFound
+ },
+ { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+ )
+
+ /** logs removeRemoteInput invocation of [RemoteInputController] */
+ @JvmOverloads
+ fun logRemoveRemoteInput(
+ entryKey: String,
+ remoteEditImeVisible: Boolean,
+ remoteEditImeAnimatingAway: Boolean,
+ isRemoteInputActive: Boolean? = null
+ ) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = entryKey
+ bool1 = remoteEditImeVisible
+ bool2 = remoteEditImeAnimatingAway
+ str2 = isRemoteInputActive?.toString() ?: "N/A"
+ },
+ {
+ "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+ ", remoteEditImeAnimatingAway: $bool2, isActive: $str2"
+ }
+ )
+
+ private companion object {
+ private const val TAG = "RemoteInputControllerLog"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 1004ec1..4d0e746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -389,11 +389,11 @@
// First check whether this notification should launch a full screen intent, and
// launch it if needed.
val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
- if (fsiDecision != null && fsiDecision.shouldLaunch) {
- mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+ mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+ if (fsiDecision.shouldLaunch) {
mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
} else if (mFlags.fsiOnDNDUpdate() &&
- fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+ fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
// If DND was the only reason this entry was suppressed, note it for potential
// reconsideration on later ranking updates.
addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
@@ -514,14 +514,24 @@
mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
if (decision.shouldLaunch) {
// Log both the launch of the full screen and also that this was via a
- // ranking update.
- mLogger.logEntryUpdatedToFullScreen(entry.key)
+ // ranking update, and finally revoke candidacy for FSI reconsideration
+ mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name)
mNotificationInterruptStateProvider.logFullScreenIntentDecision(
entry, decision)
mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+ mFSIUpdateCandidates.remove(entry.key)
// if we launch the FSI then this is no longer a candidate for HUN
continue
+ } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
+ // decision has not changed; no need to log
+ } else {
+ // some other condition is now blocking FSI; log that and revoke candidacy
+ // for FSI reconsideration
+ mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name)
+ mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+ entry, decision)
+ mFSIUpdateCandidates.remove(entry.key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 2c6bf6b..e936559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,11 +70,21 @@
})
}
- fun logEntryUpdatedToFullScreen(key: String) {
+ fun logEntryUpdatedToFullScreen(key: String, reason: String) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
+ str2 = reason
}, {
- "updating entry to launch full screen intent: $str1"
+ "updating entry to launch full screen intent: $str1 because $str2"
+ })
+ }
+
+ fun logEntryDisqualifiedFromFullScreen(key: String, reason: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ str2 = reason
+ }, {
+ "updated entry no longer qualifies for full screen intent: $str1 because $str2"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 9001470..5ba8801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.interruption;
+import androidx.annotation.NonNull;
+
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
@@ -153,7 +155,8 @@
* @param entry the entry to evaluate
* @return FullScreenIntentDecision representing the decision for whether to show the intent
*/
- FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry);
+ @NonNull
+ FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry);
/**
* Write the full screen launch decision for the given entry to logs.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 9f45b9d..6f4eed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -28,12 +28,11 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
-import android.util.Log;
+
+import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
@@ -68,7 +67,6 @@
private final KeyguardStateController mKeyguardStateController;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
- private final IDreamManager mDreamManager;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatteryController mBatteryController;
private final HeadsUpManager mHeadsUpManager;
@@ -110,7 +108,6 @@
public NotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
PowerManager powerManager,
- IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
@@ -124,7 +121,6 @@
UserTracker userTracker) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
- mDreamManager = dreamManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mStatusBarStateController = statusBarStateController;
@@ -232,6 +228,7 @@
// suppressor.
//
// If the entry was not suppressed by DND, just returns the given decision.
+ @NonNull
private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision,
boolean suppressedByDND) {
if (suppressedByDND) {
@@ -243,7 +240,7 @@
}
@Override
- public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+ public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) {
if (entry.getSbn().getNotification().fullScreenIntent == null) {
if (entry.isStickyAndNotDemoted()) {
return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN;
@@ -284,7 +281,9 @@
}
// If the device is currently dreaming, then launch the FullScreenIntent
- if (isDreaming()) {
+ // We avoid using IDreamManager#isDreaming here as that method will return false during
+ // the dream's wake-up phase.
+ if (mStatusBarStateController.isDreaming()) {
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING,
suppressedByDND);
}
@@ -336,64 +335,32 @@
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
switch (decision) {
- case NO_FSI_SHOW_STICKY_HUN:
- mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN");
- return;
case NO_FULL_SCREEN_INTENT:
- return;
- case NO_FSI_SUPPRESSED_BY_DND:
- case NO_FSI_SUPPRESSED_ONLY_BY_DND:
- mLogger.logNoFullscreen(entry, "Suppressed by DND");
- return;
- case NO_FSI_NOT_IMPORTANT_ENOUGH:
- mLogger.logNoFullscreen(entry, "Not important enough");
+ // explicitly prevent logging for this (frequent) case
return;
case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"groupAlertBehavior");
mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
packageName);
- mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
- return;
- case FSI_DEVICE_NOT_INTERACTIVE:
- mLogger.logFullscreen(entry, "Device is not interactive");
- return;
- case FSI_DEVICE_IS_DREAMING:
- mLogger.logFullscreen(entry, "Device is dreaming");
- return;
- case FSI_KEYGUARD_SHOWING:
- mLogger.logFullscreen(entry, "Keyguard is showing");
- return;
- case NO_FSI_EXPECTED_TO_HUN:
- mLogger.logNoFullscreen(entry, "Expected to HUN");
- return;
- case FSI_KEYGUARD_OCCLUDED:
- mLogger.logFullscreen(entry,
- "Expected not to HUN while keyguard occluded");
- return;
- case FSI_LOCKED_SHADE:
- mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+ mLogger.logNoFullscreenWarning(entry,
+ decision + ": GroupAlertBehavior will prevent HUN");
return;
case NO_FSI_NO_HUN_OR_KEYGUARD:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"no hun or keyguard");
mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
- mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ mLogger.logNoFullscreenWarning(entry,
+ decision + ": Expected not to HUN while not on keyguard");
return;
- case FSI_EXPECTED_NOT_TO_HUN:
- mLogger.logFullscreen(entry, "Expected not to HUN");
+ default:
+ if (decision.shouldLaunch) {
+ mLogger.logFullscreen(entry, decision.name());
+ } else {
+ mLogger.logNoFullscreen(entry, decision.name());
+ }
}
}
-
- private boolean isDreaming() {
- try {
- return mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to query dream manager.", e);
- return false;
- }
- }
-
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
@@ -443,7 +410,7 @@
return false;
}
- boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
+ boolean inUse = mPowerManager.isScreenOn() && !mStatusBarStateController.isDreaming();
if (!inUse) {
if (log) mLogger.logNoHeadsUpNotInUse(entry);
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 a5b7e94..33cbf06 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
@@ -184,7 +184,6 @@
private int mMaxSmallHeight;
private int mMaxSmallHeightLarge;
private int mMaxExpandedHeight;
- private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -3065,14 +3064,6 @@
return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
}
- @Override
- public int getExtraBottomPadding() {
- if (mIsSummaryWithChildren && isGroupExpanded()) {
- return mIncreasedPaddingBetweenElements;
- }
- return 0;
- }
-
public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
mIsInlineReplyAnimationFlagEnabled = isEnabled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 25c7264..9df6ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -451,7 +451,7 @@
protected void updateClipping() {
if (mClipToActualHeight && shouldClipToActualHeight()) {
int top = getClipTopAmount();
- int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+ int bottom = Math.max(Math.max(getActualHeight()
- mClipBottomAmount, top), mMinimumHeightForClipping);
mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
setClipBounds(mClipRect);
@@ -592,13 +592,6 @@
}
/**
- * @return padding used to alter how much of the view is clipped.
- */
- public int getExtraBottomPadding() {
- return 0;
- }
-
- /**
* @return true if the group's expansion state is changing, false otherwise.
*/
public boolean isGroupExpansionChanging() {
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 d93c12b..5834dcb 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
@@ -2055,6 +2055,23 @@
pw.print("null");
}
pw.println();
+
+ pw.print("RemoteInputViews { ");
+ pw.print(" visibleType: " + mVisibleType);
+ if (mHeadsUpRemoteInputController != null) {
+ pw.print(", headsUpRemoteInputController.isActive: "
+ + mHeadsUpRemoteInputController.isActive());
+ } else {
+ pw.print(", headsUpRemoteInputController: null");
+ }
+
+ if (mExpandedRemoteInputController != null) {
+ pw.print(", expandedRemoteInputController.isActive: "
+ + mExpandedRemoteInputController.isActive());
+ } else {
+ pw.print(", expandedRemoteInputController: null");
+ }
+ pw.println(" }");
}
/** Add any existing SmartReplyView to the dump */
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 e2e2a23..c0aed7a 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
@@ -732,19 +732,28 @@
return;
}
// TODO: move this logic to controller, which will invoke updateFooterView directly
- boolean showDismissView = mClearAllEnabled &&
- mController.hasActiveClearableNotifications(ROWS_ALL);
- boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
- && mIsCurrentUserSetup // see: b/193149550
+ final boolean showHistory = mController.isHistoryEnabled();
+ final boolean showDismissView = shouldShowDismissView();
+
+ updateFooterView(shouldShowFooterView(showDismissView)/* visible */,
+ showDismissView /* showDismissView */,
+ showHistory/* showHistory */);
+ }
+
+ private boolean shouldShowDismissView() {
+ return mClearAllEnabled
+ && mController.hasActiveClearableNotifications(ROWS_ALL);
+ }
+
+ private boolean shouldShowFooterView(boolean showDismissView) {
+ return (showDismissView || mController.getVisibleNotificationCount() > 0)
+ && mIsCurrentUserSetup // see: b/193149550
&& !onKeyguard()
&& mUpcomingStatusBarState != StatusBarState.KEYGUARD
// quick settings don't affect notifications when not in full screen
&& (mQsExpansionFraction != 1 || !mQsFullScreen)
&& !mScreenOffAnimationController.shouldHideNotificationsFooter()
&& !mIsRemoteInputActive;
- boolean showHistory = mController.isHistoryEnabled();
-
- updateFooterView(showFooterView, showDismissView, showHistory);
}
/**
@@ -4499,7 +4508,7 @@
expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
} else {
float yLocation = previous.getTranslationY() + previous.getActualHeight() -
- expandableView.getTranslationY() - previous.getExtraBottomPadding();
+ expandableView.getTranslationY();
expandableView.setFakeShadowIntensity(
diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
previous.getOutlineAlpha(), (int) yLocation,
@@ -5278,29 +5287,71 @@
});
pw.println();
pw.println("Contents:");
- DumpUtilsKt.withIncreasedIndent(pw, () -> {
- int childCount = getChildCount();
- pw.println("Number of children: " + childCount);
- pw.println();
+ DumpUtilsKt.withIncreasedIndent(
+ pw,
+ () -> {
+ int childCount = getChildCount();
+ pw.println("Number of children: " + childCount);
+ pw.println();
- for (int i = 0; i < childCount; i++) {
- ExpandableView child = getChildAtIndex(i);
- child.dump(pw, args);
- pw.println();
- }
- int transientViewCount = getTransientViewCount();
- pw.println("Transient Views: " + transientViewCount);
- for (int i = 0; i < transientViewCount; i++) {
- ExpandableView child = (ExpandableView) getTransientView(i);
- child.dump(pw, args);
- }
- View swipedView = mSwipeHelper.getSwipedView();
- pw.println("Swiped view: " + swipedView);
- if (swipedView instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) swipedView;
- expandableView.dump(pw, args);
- }
- });
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView child = getChildAtIndex(i);
+ child.dump(pw, args);
+ if (child instanceof FooterView) {
+ DumpUtilsKt.withIncreasedIndent(pw, () -> dumpFooterViewVisibility(pw));
+ }
+ pw.println();
+ }
+ int transientViewCount = getTransientViewCount();
+ pw.println("Transient Views: " + transientViewCount);
+ for (int i = 0; i < transientViewCount; i++) {
+ ExpandableView child = (ExpandableView) getTransientView(i);
+ child.dump(pw, args);
+ }
+ View swipedView = mSwipeHelper.getSwipedView();
+ pw.println("Swiped view: " + swipedView);
+ if (swipedView instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) swipedView;
+ expandableView.dump(pw, args);
+ }
+ });
+ }
+
+ private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+ final boolean showDismissView = shouldShowDismissView();
+
+ pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
+ DumpUtilsKt.withIncreasedIndent(
+ pw,
+ () -> {
+ pw.println("showDismissView: " + showDismissView);
+ DumpUtilsKt.withIncreasedIndent(
+ pw,
+ () -> {
+ pw.println("mClearAllEnabled: " + mClearAllEnabled);
+ pw.println(
+ "hasActiveClearableNotifications: "
+ + mController.hasActiveClearableNotifications(
+ ROWS_ALL));
+ });
+ pw.println();
+ pw.println("showHistory: " + mController.isHistoryEnabled());
+ pw.println();
+ pw.println(
+ "visibleNotificationCount: "
+ + mController.getVisibleNotificationCount());
+ pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
+ pw.println("onKeyguard: " + onKeyguard());
+ pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
+ pw.println("mQsExpansionFraction: " + mQsExpansionFraction);
+ pw.println("mQsFullScreen: " + mQsFullScreen);
+ pw.println(
+ "mScreenOffAnimationController"
+ + ".shouldHideNotificationsFooter: "
+ + mScreenOffAnimationController
+ .shouldHideNotificationsFooter());
+ pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive);
+ });
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f1fc386..f866d65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.IntentFilter
-import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
@@ -28,12 +27,12 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_FLASH
+import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
@@ -59,16 +58,14 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
/**
@@ -100,8 +97,6 @@
}
}
- private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
/**
* This flow defines the single shared connection to system_server via TelephonyCallback. Any
* new callback should be added to this listener and funneled through callbackEvents via a data
@@ -109,9 +104,15 @@
*
* The reason we need to do this is because TelephonyManager limits the number of registered
* listeners per-process, so we don't want to create a new listener for every callback.
+ *
+ * A note on the design for back pressure here: We use the [coalesce] operator here to change
+ * the backpressure strategy to store exactly the last callback event of _each type_ here, as
+ * opposed to the default strategy which is to drop the oldest event (regardless of type). This
+ * means that we should never miss any single event as long as the flow has been started.
*/
- private val callbackEvents: SharedFlow<CallbackEvent> =
- conflatedCallbackFlow {
+ private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
+ val initial = TelephonyCallbackState()
+ callbackFlow {
val callback =
object :
TelephonyCallback(),
@@ -165,48 +166,50 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
- .shareIn(scope, SharingStarted.WhileSubscribed())
+ .scan(initial = initial) { state, event -> state.applyEvent(event) }
+ .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
+ }
override val isEmergencyOnly =
callbackEvents
- .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isRoaming =
callbackEvents
- .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.roaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val operatorAlphaShort =
callbackEvents
- .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.operatorAlphaShort }
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val isInService =
callbackEvents
- .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .mapNotNull { it.onServiceStateChanged }
.map { Utils.isInService(it.serviceState) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isGsm =
callbackEvents
- .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .mapNotNull { it.onSignalStrengthChanged }
.map { it.signalStrength.isGsm }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val cdmaLevel =
callbackEvents
- .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .mapNotNull { it.onSignalStrengthChanged }
.map {
it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
strengths ->
if (strengths.isNotEmpty()) {
strengths[0].level
} else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ SIGNAL_STRENGTH_NONE_OR_UNKNOWN
}
}
}
@@ -214,19 +217,19 @@
override val primaryLevel =
callbackEvents
- .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .mapNotNull { it.onSignalStrengthChanged }
.map { it.signalStrength.level }
.stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val dataConnectionState =
callbackEvents
- .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+ .mapNotNull { it.onDataConnectionStateChanged }
.map { it.dataState.toDataConnectionType() }
.stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
override val dataActivityDirection =
callbackEvents
- .filterIsInstance<CallbackEvent.OnDataActivity>()
+ .mapNotNull { it.onDataActivity }
.map { it.direction.toMobileDataActivityModel() }
.stateIn(
scope,
@@ -236,28 +239,26 @@
override val carrierNetworkChangeActive =
callbackEvents
- .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+ .mapNotNull { it.onCarrierNetworkChange }
.map { it.active }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val resolvedNetworkType =
callbackEvents
- .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+ .mapNotNull { it.onDisplayInfoChanged }
.map {
- if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
- UnknownNetworkType
- } else if (
- it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
- )
- } else {
+ if (it.telephonyDisplayInfo.overrideNetworkType != OVERRIDE_NETWORK_TYPE_NONE) {
OverrideNetworkType(
mobileMappingsProxy.toIconKeyOverride(
it.telephonyDisplayInfo.overrideNetworkType
)
)
+ } else if (it.telephonyDisplayInfo.networkType != NETWORK_TYPE_UNKNOWN) {
+ DefaultNetworkType(
+ mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+ )
+ } else {
+ UnknownNetworkType
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
@@ -282,7 +283,10 @@
override val cdmaRoaming: StateFlow<Boolean> =
telephonyPollingEvent
- .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+ .mapLatest {
+ val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+ cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val networkName: StateFlow<NetworkNameModel> =
@@ -300,7 +304,8 @@
override val dataEnabled = run {
val initial = telephonyManager.isDataConnectionAllowed
callbackEvents
- .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
+ .mapNotNull { it.onDataEnabledChanged }
+ .map { it.enabled }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
@@ -344,12 +349,41 @@
* Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
* shared flow and then split them back out into other flows.
*/
-private sealed interface CallbackEvent {
+sealed interface CallbackEvent {
+ data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+ data class OnDataActivity(val direction: Int) : CallbackEvent
+ data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+ data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+ data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
- data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
- data class OnDataActivity(val direction: Int) : CallbackEvent
- data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
- data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
- data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+}
+
+/**
+ * A simple box type for 1-to-1 mapping of [CallbackEvent] to the batched event. Used in conjunction
+ * with [scan] to make sure we don't drop important callbacks due to late subscribers
+ */
+data class TelephonyCallbackState(
+ val onDataActivity: CallbackEvent.OnDataActivity? = null,
+ val onCarrierNetworkChange: CallbackEvent.OnCarrierNetworkChange? = null,
+ val onDataConnectionStateChanged: CallbackEvent.OnDataConnectionStateChanged? = null,
+ val onDataEnabledChanged: CallbackEvent.OnDataEnabledChanged? = null,
+ val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
+ val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
+ val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
+) {
+ fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
+ return when (event) {
+ is CallbackEvent.OnCarrierNetworkChange -> copy(onCarrierNetworkChange = event)
+ is CallbackEvent.OnDataActivity -> copy(onDataActivity = event)
+ is CallbackEvent.OnDataConnectionStateChanged ->
+ copy(onDataConnectionStateChanged = event)
+ is CallbackEvent.OnDataEnabledChanged -> copy(onDataEnabledChanged = event)
+ is CallbackEvent.OnDisplayInfoChanged -> copy(onDisplayInfoChanged = event)
+ is CallbackEvent.OnServiceStateChanged -> {
+ copy(onServiceStateChanged = event)
+ }
+ is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3c7d092..95cc12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1416,11 +1416,7 @@
@Override
public void onAnimationCancel(@NonNull Animator animation) {
mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL);
- Log.i(TAG, "onAnimationCancel");
-
- // We can only have one animation listener for cancel, so the jank listener should
- // also call for cleanup.
- finishDismiss();
+ Log.d(TAG, "onAnimationCancel");
}
@Override
@@ -1529,7 +1525,12 @@
.setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
- finishDismiss();
+ mController.notifyVisible(false);
+ mDialog.dismiss();
+ tryToRemoveCaptionsTooltip();
+ mIsAnimatingDismiss = false;
+
+ hideRingerDrawer();
}, 50));
if (!shouldSlideInVolumeTray()) {
animator.translationX(
@@ -1547,18 +1548,6 @@
Trace.endSection();
}
- /**
- * Clean up and hide volume dialog. Called when animation is finished/cancelled.
- */
- private void finishDismiss() {
- mController.notifyVisible(false);
- mDialog.dismiss();
- tryToRemoveCaptionsTooltip();
- mIsAnimatingDismiss = false;
-
- hideRingerDrawer();
- }
-
private boolean showActiveStreamOnly() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|| mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 0e837d2..a35e5b5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -18,12 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,6 +40,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
@Mock
@@ -45,14 +49,14 @@
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
-
private KeyguardMessageAreaController mMessageAreaController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mMessageAreaController = new KeyguardMessageAreaController.Factory(
- mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
+ mKeyguardUpdateMonitor, mConfigurationController).create(
+ mKeyguardMessageArea);
}
@Test
@@ -89,6 +93,19 @@
}
@Test
+ public void testSetMessage_AnnounceForAccessibility() {
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ when(mKeyguardMessageArea.getText()).thenReturn("abc");
+ mMessageAreaController.setMessage("abc");
+
+ verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true);
+ verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
+ verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong());
+ argumentCaptor.getValue().run();
+ verify(mKeyguardMessageArea).announceForAccessibility("abc");
+ }
+
+ @Test
public void testSetBouncerVisible() {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 38d3a3e..f966eb3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -66,6 +66,7 @@
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -619,13 +620,26 @@
@Test
public void testReinflateViewFlipper() {
- mKeyguardSecurityContainerController.reinflateViewFlipper();
+ mKeyguardSecurityContainerController.reinflateViewFlipper(() -> {});
verify(mKeyguardSecurityViewFlipperController).clearViews();
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
any(KeyguardSecurityCallback.class));
}
@Test
+ public void testReinflateViewFlipper_asyncBouncerFlagOn() {
+ when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true);
+ KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener =
+ () -> {
+ };
+ mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedListener);
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
+ any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class), eq(onViewInflatedListener));
+ }
+
+ @Test
public void testSideFpsControllerShow() {
mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
verify(mSideFpsController).show(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 1614b57..afb54d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -31,10 +31,12 @@
import android.view.ViewGroup;
import android.view.WindowInsetsController;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import org.junit.Before;
import org.junit.Rule;
@@ -57,6 +59,8 @@
@Mock
private LayoutInflater mLayoutInflater;
@Mock
+ private AsyncLayoutInflater mAsyncLayoutInflater;
+ @Mock
private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
@Mock
private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
@@ -70,6 +74,8 @@
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityCallback mKeyguardSecurityCallback;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@@ -82,10 +88,11 @@
when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
.thenReturn(mEmergencyButtonController);
+ when(mView.getContext()).thenReturn(getContext());
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
- mLayoutInflater, mKeyguardSecurityViewControllerFactory,
- mEmergencyButtonControllerFactory);
+ mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
+ mEmergencyButtonControllerFactory, mFeatureFlags);
}
@Test
@@ -108,6 +115,14 @@
}
@Test
+ public void asynchronouslyInflateView() {
+ mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+ mKeyguardSecurityCallback, null);
+ verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any(
+ AsyncLayoutInflater.OnInflateFinishedListener.class));
+ }
+
+ @Test
public void onDensityOrFontScaleChanged() {
mKeyguardSecurityViewFlipperController.clearViews();
verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
new file mode 100644
index 0000000..070cad7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.animation
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FontVariationUtilsTest : SysuiTestCase() {
+ @Test
+ fun testUpdateFontVariation_getCorrectFvarStr() {
+ val fontVariationUtils = FontVariationUtils()
+ val initFvar =
+ fontVariationUtils.updateFontVariation(
+ weight = 100,
+ width = 100,
+ opticalSize = -1,
+ roundness = 100
+ )
+ Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar)
+ val updatedFvar =
+ fontVariationUtils.updateFontVariation(
+ weight = 200,
+ width = 100,
+ opticalSize = 0,
+ roundness = 100
+ )
+ Assert.assertEquals(
+ "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100",
+ updatedFvar
+ )
+ }
+
+ @Test
+ fun testStyleValueUnchange_getBlankStr() {
+ val fontVariationUtils = FontVariationUtils()
+ fontVariationUtils.updateFontVariation(
+ weight = 100,
+ width = 100,
+ opticalSize = 0,
+ roundness = 100
+ )
+ val updatedFvar1 =
+ fontVariationUtils.updateFontVariation(
+ weight = 100,
+ width = 100,
+ opticalSize = 0,
+ roundness = 100
+ )
+ Assert.assertEquals("", updatedFvar1)
+ val updatedFvar2 = fontVariationUtils.updateFontVariation()
+ Assert.assertEquals("", updatedFvar2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index b389558..d7aa6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -19,7 +19,6 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Typeface
-import android.graphics.fonts.FontVariationAxis
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
@@ -180,71 +179,4 @@
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
-
- @Test
- fun testSetTextStyle_addWeight() {
- testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
- }
-
- @Test
- fun testSetTextStyle_changeWeight() {
- testWeightChange(
- "'wght' 500",
- 100,
- FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
- )
- }
-
- @Test
- fun testSetTextStyle_addWeightWithOtherAxis() {
- testWeightChange(
- "'wdth' 100",
- 100,
- FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
- )
- }
-
- @Test
- fun testSetTextStyle_changeWeightWithOtherAxis() {
- testWeightChange(
- "'wght' 500, 'wdth' 100",
- 100,
- FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
- )
- }
-
- private fun testWeightChange(
- initialFontVariationSettings: String,
- weight: Int,
- expectedFontVariationSettings: Array<FontVariationAxis>
- ) {
- val layout = makeLayout("Hello, World", PAINT)
- val valueAnimator = mock(ValueAnimator::class.java)
- val textInterpolator = mock(TextInterpolator::class.java)
- val paint =
- TextPaint().apply {
- typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
- fontVariationSettings = initialFontVariationSettings
- }
- `when`(textInterpolator.targetPaint).thenReturn(paint)
-
- val textAnimator =
- TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
- textAnimator.setTextStyle(weight = weight, animate = false)
-
- val resultFontVariationList =
- FontVariationAxis.fromFontVariationSettings(
- textInterpolator.targetPaint.fontVariationSettings
- )
- expectedFontVariationSettings.forEach { expectedAxis ->
- val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
- assertThat(resultAxis).isNotNull()
- if (resultAxis != null) {
- assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
- }
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e1c7417..c068efb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -35,7 +35,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -930,6 +929,15 @@
assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
}
+ @Test
+ public void testCloseDialog_whenGlobalActionsMenuShown() throws Exception {
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+ mAuthController.handleShowGlobalActionsMenu();
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+ eq(null) /* credentialAttestation */);
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 86fb279..786cb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -138,7 +138,7 @@
// WHEN the bouncer expansion is VISIBLE
val job = mController.listenForBouncerExpansion(this)
- keyguardBouncerRepository.setPrimaryVisible(true)
+ keyguardBouncerRepository.setPrimaryShow(true)
keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 5a613aa..590989d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -45,6 +45,7 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -234,6 +235,36 @@
}
@Test
+ fun dialogPositiveButtonWhenCalledOnCompleteSettingIsTrue() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ doAnswer { assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() }
+ .`when`(completedRunnable)
+ .invoke()
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogPositiveCancelKeyguardStillCallsOnComplete() {
+ `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+ .thenAnswer { (it.arguments[1] as Runnable).run() }
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
fun dialogCancelDoesntChangeSetting() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 3f61bf7..10757ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -347,7 +347,7 @@
whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
val panel = SelectedItem.PanelItem("App name", componentName)
preferredPanelRepository.setSelectedComponent(
- SelectedComponentRepository.SelectedComponent(panel)
+ SelectedComponentRepository.SelectedComponent(panel)
)
underTest.show(parent, {}, context)
underTest.startRemovingApp(componentName, "Test App")
@@ -362,6 +362,26 @@
}
@Test
+ fun testCancelRemovingAppsDoesntRemoveFavorite() {
+ val componentName = ComponentName(context, "cls")
+ whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
+ val panel = SelectedItem.PanelItem("App name", componentName)
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
+ underTest.show(parent, {}, context)
+ underTest.startRemovingApp(componentName, "Test App")
+
+ fakeDialogController.clickNeutral()
+
+ verify(controlsController, never()).removeFavorites(eq(componentName))
+ assertThat(underTest.getPreferredSelectedItem(emptyList())).isEqualTo(panel)
+ assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isTrue()
+ assertThat(preferredPanelRepository.getSelectedComponent())
+ .isEqualTo(SelectedComponentRepository.SelectedComponent(panel))
+ }
+
+ @Test
fun testHideCancelsTheRemoveAppDialog() {
val componentName = ComponentName(context, "cls")
underTest.show(parent, {}, context)
@@ -372,10 +392,42 @@
verify(fakeDialogController.dialog).cancel()
}
+ @Test
+ fun testOnRotationWithPanelUpdateBoundsCalled() {
+ mockLayoutInflater()
+ val packageName = "pkg"
+ `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
+ val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
+ val serviceInfo = setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
+ verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
+
+ val taskView: TaskView = mock {
+ `when`(this.post(any())).thenAnswer {
+ uiExecutor.execute(it.arguments[0] as Runnable)
+ true
+ }
+ }
+
+ taskViewConsumerCaptor.value.accept(taskView)
+
+ underTest.onOrientationChange()
+ verify(taskView).onLocationChanged()
+ }
+
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName(context, "activity")
preferredPanelRepository.setSelectedComponent(
- SelectedComponentRepository.SelectedComponent(panel)
+ SelectedComponentRepository.SelectedComponent(panel)
)
return ControlsServiceInfo(panel.componentName, panel.appName, activity)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index de04ef8..9df7992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -152,4 +152,12 @@
listenerCaptor.value.onTaskRemovalStarted(0)
verify(taskView).release()
}
+
+ @Test
+ fun testOnRefreshBounds() {
+ underTest.launchTaskView()
+
+ underTest.refreshBounds()
+ verify(taskView).onLocationChanged()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index aa17d49..ed73797 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,7 +20,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -63,6 +65,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -295,6 +298,7 @@
// Inform the overlay service of dream starting.
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
assertThat(mService.shouldShowComplications()).isTrue();
}
@@ -338,6 +342,48 @@
}
@Test
+ public void testImmediateEndDream() throws Exception {
+ final IDreamOverlayClient client = getClient();
+
+ // Start the dream, but don't execute any Runnables put on the executor yet. We delay
+ // executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
+ // starts and finishes in the proper order even if Runnables are delayed.
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ // Immediately end the dream.
+ client.endDream();
+ // Run any scheduled Runnables.
+ mMainExecutor.runAllReady();
+
+ // The overlay starts then finishes.
+ InOrder inOrder = inOrder(mWindowManager);
+ inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
+ }
+
+ @Test
+ public void testEndDreamDuringStartDream() throws Exception {
+ final IDreamOverlayClient client = getClient();
+
+ // Schedule the endDream call in the middle of the startDream implementation, as any
+ // ordering is possible.
+ doAnswer(invocation -> {
+ client.endDream();
+ return null;
+ }).when(mStateController).setOverlayActive(true);
+
+ // Start the dream.
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // The overlay starts then finishes.
+ InOrder inOrder = inOrder(mWindowManager);
+ inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
+ }
+
+ @Test
public void testDestroy() throws RemoteException {
final IDreamOverlayClient client = getClient();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index b3329eb..0e16b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.dreams.complication;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,7 +35,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Monitor;
import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -57,8 +56,7 @@
private Context mContext;
@Mock
private DreamBackend mDreamBackend;
- @Mock
- private SecureSettings mSecureSettings;
+ private FakeSettings mSecureSettings;
@Mock
private DreamOverlayStateController mDreamOverlayStateController;
@Captor
@@ -74,6 +72,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+ mSecureSettings = new FakeSettings();
mMonitor = SelfExecutingMonitor.createInstance();
mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
@@ -100,19 +99,15 @@
when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>(Arrays.asList(
DreamBackend.COMPLICATION_TYPE_TIME, DreamBackend.COMPLICATION_TYPE_WEATHER,
DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)));
- final ContentObserver settingsObserver = captureSettingsObserver();
- settingsObserver.onChange(false);
+
+ // Update the setting to trigger any content observers
+ mSecureSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, true,
+ UserHandle.myUserId());
mExecutor.runAllReady();
verify(mDreamOverlayStateController).setAvailableComplicationTypes(
Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_WEATHER
| Complication.COMPLICATION_TYPE_AIR_QUALITY);
}
-
- private ContentObserver captureSettingsObserver() {
- verify(mSecureSettings).registerContentObserverForUser(
- eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED),
- mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId()));
- return mSettingsObserverCaptor.getValue();
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 2bcd75b..18f7db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -37,6 +37,7 @@
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -53,7 +54,7 @@
*/
@SmallTest
class FeatureFlagsDebugTest : SysuiTestCase() {
- private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
+ private lateinit var featureFlagsDebug: FeatureFlagsDebug
@Mock
private lateinit var flagManager: FlagManager
@@ -85,7 +86,7 @@
flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
- mFeatureFlagsDebug = FeatureFlagsDebug(
+ featureFlagsDebug = FeatureFlagsDebug(
flagManager,
mockContext,
globalSettings,
@@ -95,7 +96,7 @@
flagMap,
restarter
)
- mFeatureFlagsDebug.init()
+ featureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
verify(mockContext).registerReceiver(
@@ -116,7 +117,7 @@
whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
ReleasedFlag(
2,
name = "2",
@@ -125,7 +126,7 @@
)
).isTrue()
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
UnreleasedFlag(
3,
name = "3",
@@ -134,7 +135,7 @@
)
).isTrue()
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
ReleasedFlag(
4,
name = "4",
@@ -143,7 +144,7 @@
)
).isFalse()
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
UnreleasedFlag(
5,
name = "5",
@@ -157,8 +158,8 @@
fun teamFoodFlag_False() {
whenever(flagManager.readFlagValue<Boolean>(
eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -169,8 +170,8 @@
fun teamFoodFlag_True() {
whenever(flagManager.readFlagValue<Boolean>(
eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -185,8 +186,8 @@
.thenReturn(false)
whenever(flagManager.readFlagValue<Boolean>(
eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -205,7 +206,7 @@
whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
ResourceBooleanFlag(
1,
"1",
@@ -214,16 +215,16 @@
)
)
).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
+ featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
+ featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
}
}
@@ -236,11 +237,11 @@
return@thenAnswer it.getArgument(1)
}
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
+ assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
assertThat(
- mFeatureFlagsDebug.isEnabled(
+ featureFlagsDebug.isEnabled(
SysPropBooleanFlag(
4,
"d",
@@ -249,17 +250,17 @@
)
)
).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
+ assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
}
@Test
fun readStringFlag() {
whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
+ assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
+ assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
+ assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
+ assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
}
@Test
@@ -276,7 +277,7 @@
whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
assertThat(
- mFeatureFlagsDebug.getString(
+ featureFlagsDebug.getString(
ResourceStringFlag(
1,
"1",
@@ -286,7 +287,7 @@
)
).isEqualTo("")
assertThat(
- mFeatureFlagsDebug.getString(
+ featureFlagsDebug.getString(
ResourceStringFlag(
2,
"2",
@@ -296,7 +297,7 @@
)
).isEqualTo("resource2")
assertThat(
- mFeatureFlagsDebug.getString(
+ featureFlagsDebug.getString(
ResourceStringFlag(
3,
"3",
@@ -307,15 +308,15 @@
).isEqualTo("override3")
Assert.assertThrows(NullPointerException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
+ featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
}
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
+ featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
+ featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
}
}
@@ -323,10 +324,10 @@
fun readIntFlag() {
whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
+ assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
+ assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
+ assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
+ assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
}
@Test
@@ -342,17 +343,17 @@
whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
+ assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
+ assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
+ assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
Assert.assertThrows(NotFoundException::class.java) {
- mFeatureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
+ featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NotFoundException::class.java) {
- mFeatureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
+ featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
}
}
@@ -432,11 +433,11 @@
whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
// gets the flag & cache it
- assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+ assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
// hit the cache
- assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+ assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
verifyNoMoreInteractions(flagManager)
// set the flag
@@ -444,7 +445,7 @@
verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
- assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
+ assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("new")
verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
}
@@ -454,7 +455,7 @@
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
- assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse()
+ assertThat(featureFlagsDebug.isEnabled(flag)).isFalse()
}
@Test
@@ -462,7 +463,33 @@
val flag = UnreleasedFlag(100, name = "100", namespace = "test")
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
- assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(flag)).isTrue()
+ }
+
+ @Test
+ fun serverSide_OverrideUncached_NoRestart() {
+ // No one has read the flag, so it's not in the cache.
+ serverFlagReader.setFlagValue(
+ teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+ verify(restarter, never()).restartSystemUI(anyString())
+ }
+
+ @Test
+ fun serverSide_Override_Restarts() {
+ // Read it to put it in the cache.
+ featureFlagsDebug.isEnabled(teamfoodableFlagA)
+ serverFlagReader.setFlagValue(
+ teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+ verify(restarter).restartSystemUI(anyString())
+ }
+
+ @Test
+ fun serverSide_RedundantOverride_NoRestart() {
+ // Read it to put it in the cache.
+ featureFlagsDebug.isEnabled(teamfoodableFlagA)
+ serverFlagReader.setFlagValue(
+ teamfoodableFlagA.namespace, teamfoodableFlagA.name, teamfoodableFlagA.default)
+ verify(restarter, never()).restartSystemUI(anyString())
}
@Test
@@ -482,13 +509,13 @@
.thenReturn("override7")
// WHEN the flags have been accessed
- assertThat(mFeatureFlagsDebug.isEnabled(flag1)).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(flag2)).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(flag3)).isFalse()
- assertThat(mFeatureFlagsDebug.getString(flag4)).isEmpty()
- assertThat(mFeatureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
- assertThat(mFeatureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
- assertThat(mFeatureFlagsDebug.getString(flag7)).isEqualTo("override7")
+ assertThat(featureFlagsDebug.isEnabled(flag1)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(flag2)).isTrue()
+ assertThat(featureFlagsDebug.isEnabled(flag3)).isFalse()
+ assertThat(featureFlagsDebug.getString(flag4)).isEmpty()
+ assertThat(featureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
+ assertThat(featureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
+ assertThat(featureFlagsDebug.getString(flag7)).isEqualTo("override7")
// THEN the dump contains the flags and the default values
val dump = dumpToString()
@@ -527,7 +554,7 @@
private fun dumpToString(): String {
val sw = StringWriter()
val pw = PrintWriter(sw)
- mFeatureFlagsDebug.dump(pw, emptyArray<String>())
+ featureFlagsDebug.dump(pw, emptyArray<String>())
pw.flush()
return sw.toString()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 4c6028c..917147b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -24,6 +24,8 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@@ -33,7 +35,7 @@
*/
@SmallTest
class FeatureFlagsReleaseTest : SysuiTestCase() {
- private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease
+ private lateinit var featureFlagsRelease: FeatureFlagsRelease
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@@ -41,15 +43,21 @@
private val flagMap = mutableMapOf<String, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
+
+ private val flagA = ReleasedFlag(501, name = "a", namespace = "test")
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mFeatureFlagsRelease = FeatureFlagsRelease(
+ flagMap.put(flagA.name, flagA)
+ featureFlagsRelease = FeatureFlagsRelease(
mResources,
mSystemProperties,
serverFlagReader,
flagMap,
restarter)
+
+ featureFlagsRelease.init()
}
@Test
@@ -60,7 +68,7 @@
val flagNamespace = "test"
val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
- assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
+ assertThat(featureFlagsRelease.isEnabled(flag)).isTrue()
}
@Test
@@ -70,16 +78,16 @@
whenever(mResources.getString(1003)).thenReturn(null)
whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
- assertThat(mFeatureFlagsRelease.getString(
+ assertThat(featureFlagsRelease.getString(
ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
- assertThat(mFeatureFlagsRelease.getString(
+ assertThat(featureFlagsRelease.getString(
ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
assertThrows(NullPointerException::class.java) {
- mFeatureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
+ featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
}
assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
+ featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
}
}
@@ -92,7 +100,7 @@
val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
- assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
+ assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
}
@Test
@@ -101,7 +109,7 @@
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
- assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
+ assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
}
@Test
@@ -110,6 +118,32 @@
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
- assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
+ assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
+ }
+
+ @Test
+ fun serverSide_OverrideUncached_NoRestart() {
+ // No one has read the flag, so it's not in the cache.
+ serverFlagReader.setFlagValue(
+ flagA.namespace, flagA.name, !flagA.default)
+ Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
+ }
+
+ @Test
+ fun serverSide_Override_Restarts() {
+ // Read it to put it in the cache.
+ featureFlagsRelease.isEnabled(flagA)
+ serverFlagReader.setFlagValue(
+ flagA.namespace, flagA.name, !flagA.default)
+ Mockito.verify(restarter).restartSystemUI(Mockito.anyString())
+ }
+
+ @Test
+ fun serverSide_RedundantOverride_NoRestart() {
+ // Read it to put it in the cache.
+ featureFlagsRelease.isEnabled(flagA)
+ serverFlagReader.setFlagValue(
+ flagA.namespace, flagA.name, flagA.default)
+ Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 2e98006..953b7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -21,11 +21,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -57,18 +59,18 @@
deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false)
executor.runAllReady()
- verify(changeListener).onChange(flag)
+ verify(changeListener).onChange(flag, "1")
}
@Test
fun testChange_ignoresListenersDuringTest() {
val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true)
- val flag = ReleasedFlag(1, "1", "test")
+ val flag = ReleasedFlag(1, "1", " test")
serverFlagReader.listenForChanges(listOf(flag), changeListener)
deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
executor.runAllReady()
- verify(changeListener, never()).onChange(flag)
+ verify(changeListener, never()).onChange(any(), anyString())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 984f4be..1044131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -192,6 +192,7 @@
)
underTest.previewManager =
KeyguardRemotePreviewManager(
+ applicationScope = testScope.backgroundScope,
previewRendererFactory = previewRendererFactory,
mainDispatcher = testDispatcher,
backgroundHandler = backgroundHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 2eabda3..e468cc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -57,7 +57,7 @@
@Test
fun changingFlowValueTriggersLogging() = runBlocking {
- underTest.setPrimaryHide(true)
- verify(bouncerLogger).logChange("", "PrimaryBouncerHide", false)
+ underTest.setPrimaryShow(true)
+ verify(bouncerLogger).logChange("", "PrimaryBouncerShow", false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 153439e..7f30162 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -145,7 +145,7 @@
repository.setKeyguardOccluded(true)
assertThat(secureCameraActive()).isTrue()
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
assertThat(secureCameraActive()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 5cd24e6..092fdca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -259,7 +259,7 @@
runCurrent()
// WHEN the primary bouncer is set to show
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
runCurrent()
val info =
@@ -741,7 +741,7 @@
reset(mockTransitionRepository)
// WHEN the alternateBouncer stops showing and then the primary bouncer shows
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
runCurrent()
val info =
@@ -779,7 +779,7 @@
reset(mockTransitionRepository)
// GIVEN the primary bouncer isn't showing, aod available and starting to sleep
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
keyguardRepository.setAodAvailable(true)
keyguardRepository.setWakefulnessModel(startingToSleep())
@@ -823,7 +823,7 @@
// GIVEN the primary bouncer isn't showing, aod not available and starting to sleep
// to sleep
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
keyguardRepository.setAodAvailable(false)
keyguardRepository.setWakefulnessModel(startingToSleep())
@@ -866,7 +866,7 @@
reset(mockTransitionRepository)
// GIVEN the primary bouncer isn't showing and device not sleeping
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
keyguardRepository.setWakefulnessModel(startingToWake())
// WHEN the alternateBouncer stops showing
@@ -890,7 +890,7 @@
fun `PRIMARY_BOUNCER to AOD`() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
runner.startTransition(
testScope,
TransitionInfo(
@@ -912,7 +912,7 @@
keyguardRepository.setWakefulnessModel(startingToSleep())
// WHEN the primaryBouncer stops showing
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
runCurrent()
val info =
@@ -932,7 +932,7 @@
fun `PRIMARY_BOUNCER to DOZING`() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
runner.startTransition(
testScope,
TransitionInfo(
@@ -954,7 +954,7 @@
keyguardRepository.setWakefulnessModel(startingToSleep())
// WHEN the primaryBouncer stops showing
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
runCurrent()
val info =
@@ -974,7 +974,7 @@
fun `PRIMARY_BOUNCER to LOCKSCREEN`() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
- bouncerRepository.setPrimaryVisible(true)
+ bouncerRepository.setPrimaryShow(true)
runner.startTransition(
testScope,
TransitionInfo(
@@ -995,7 +995,7 @@
keyguardRepository.setWakefulnessModel(startingToWake())
// WHEN the alternateBouncer stops showing
- bouncerRepository.setPrimaryVisible(false)
+ bouncerRepository.setPrimaryShow(false)
runCurrent()
val info =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 6b7fd61..5ec6283 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -35,7 +35,6 @@
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -92,7 +91,7 @@
keyguardBypassController,
)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- `when`(repository.primaryBouncerShow.value).thenReturn(null)
+ `when`(repository.primaryBouncerShow.value).thenReturn(false)
`when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
resources = context.orCreateTestableResources
}
@@ -101,15 +100,13 @@
fun testShow_isScrimmed() {
underTest.show(true)
verify(repository).setKeyguardAuthenticated(null)
- verify(repository).setPrimaryHide(false)
verify(repository).setPrimaryStartingToHide(false)
verify(repository).setPrimaryScrimmed(true)
verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
verify(repository).setPrimaryShowingSoon(true)
verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
- verify(repository).setPrimaryVisible(true)
- verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
+ verify(repository).setPrimaryShow(true)
verify(repository).setPrimaryShowingSoon(false)
verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
}
@@ -132,9 +129,7 @@
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
verify(repository).setPrimaryShowingSoon(false)
- verify(repository).setPrimaryVisible(false)
- verify(repository).setPrimaryHide(true)
- verify(repository).setPrimaryShow(null)
+ verify(repository).setPrimaryShow(false)
verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
}
@@ -160,9 +155,7 @@
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_HIDDEN)
- verify(repository).setPrimaryVisible(false)
- verify(repository).setPrimaryShow(null)
- verify(repository).setPrimaryHide(true)
+ verify(repository).setPrimaryShow(false)
verify(falsingCollector).onBouncerHidden()
verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
@@ -243,11 +236,11 @@
@Test
fun testIsFullShowing() {
- `when`(repository.primaryBouncerVisible.value).thenReturn(true)
+ `when`(repository.primaryBouncerShow.value).thenReturn(true)
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(underTest.isFullyShowing()).isTrue()
- `when`(repository.primaryBouncerVisible.value).thenReturn(false)
+ `when`(repository.primaryBouncerShow.value).thenReturn(false)
assertThat(underTest.isFullyShowing()).isFalse()
}
@@ -370,7 +363,7 @@
isUnlockingWithFpAllowed: Boolean,
isAnimatingAway: Boolean
) {
- `when`(repository.primaryBouncerVisible.value).thenReturn(isVisible)
+ `when`(repository.primaryBouncerShow.value).thenReturn(isVisible)
resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
`when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning)
`when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index f675e79..edac468 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -77,7 +77,7 @@
fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
val isInteractable = collectLastValue(underTest.isInteractable)
- repository.setPrimaryVisible(true)
+ repository.setPrimaryShow(true)
repository.setPanelExpansion(0.15f)
assertThat(isInteractable()).isFalse()
@@ -87,7 +87,7 @@
fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
val isInteractable = collectLastValue(underTest.isInteractable)
- repository.setPrimaryVisible(false)
+ repository.setPrimaryShow(false)
repository.setPanelExpansion(0.05f)
assertThat(isInteractable()).isFalse()
@@ -97,7 +97,7 @@
fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
var isInteractable = collectLastValue(underTest.isInteractable)
- repository.setPrimaryVisible(true)
+ repository.setPrimaryShow(true)
repository.setPanelExpansion(0.09f)
assertThat(isInteractable()).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 65e4c10..2ab1b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -93,10 +93,22 @@
}
@Test
- fun shouldUpdateSideFps() = runTest {
+ fun shouldUpdateSideFps_show() = runTest {
var count = 0
val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
- repository.setPrimaryVisible(true)
+ repository.setPrimaryShow(true)
+ // Run the tasks that are pending at this point of virtual time.
+ runCurrent()
+ assertThat(count).isEqualTo(1)
+ job.cancel()
+ }
+
+ @Test
+ fun shouldUpdateSideFps_hide() = runTest {
+ repository.setPrimaryShow(true)
+ var count = 0
+ val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+ repository.setPrimaryShow(false)
// Run the tasks that are pending at this point of virtual time.
runCurrent()
assertThat(count).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index fd353af..df13fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -94,7 +94,6 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -1763,7 +1762,7 @@
fun tapContentView_showOverLockscreen_openActivity() {
// WHEN we are on lockscreen and this activity can show over lockscreen
whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)
val clickIntent = mock(Intent::class.java)
val pendingIntent = mock(PendingIntent::class.java)
@@ -1774,16 +1773,20 @@
player.bindPlayer(data, KEY)
verify(viewHolder.player).setOnClickListener(captor.capture())
- // THEN it shows without dismissing keyguard first
+ // THEN it sends the PendingIntent without dismissing keyguard first,
+ // and does not use the Intent directly (see b/271845008)
captor.value.onClick(viewHolder.player)
- verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
+ verify(pendingIntent).send()
+ verify(pendingIntent, never()).getIntent()
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
}
@Test
fun tapContentView_noShowOverLockscreen_dismissKeyguard() {
// WHEN we are on lockscreen and the activity cannot show over lockscreen
whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(false)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
+ .thenReturn(false)
val clickIntent = mock(Intent::class.java)
val pendingIntent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index a579518..feb429d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -180,6 +180,57 @@
}
@Test
+ fun testBlockedWhenConfigurationChangesAndScreenOff() {
+ // Let's set it onto QS:
+ mediaHierarchyManager.qsExpansion = 1.0f
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
+ val observer = wakefullnessObserver.value
+ assertNotNull("lifecycle observer wasn't registered", observer)
+ observer.onStartedGoingToSleep()
+ clearInvocations(mediaCarouselController)
+ configurationController.notifyConfigurationChanged()
+ verify(mediaCarouselController, times(0))
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testAllowedWhenConfigurationChanges() {
+ // Let's set it onto QS:
+ mediaHierarchyManager.qsExpansion = 1.0f
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+ configurationController.notifyConfigurationChanged()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testAllowedWhenNotTurningOff() {
// Let's set it onto QS:
mediaHierarchyManager.qsExpansion = 1.0f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 56e060d..17d8799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,12 +16,13 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
new file mode 100644
index 0000000..ceacaf9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.repository
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeRepositoryTest : SysuiTestCase() {
+
+ private lateinit var inputProxy: MultiShadeInputProxy
+
+ @Before
+ fun setUp() {
+ inputProxy = MultiShadeInputProxy()
+ }
+
+ @Test
+ fun proxiedInput() = runTest {
+ val underTest = create()
+ val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+
+ assertWithMessage("proxiedInput should start with null").that(latest).isNull()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+ assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
+ assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
+ assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+ assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
+ }
+
+ @Test
+ fun shadeConfig_dualShadeEnabled() = runTest {
+ overrideResource(R.bool.dual_shade_enabled, true)
+ val underTest = create()
+ val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+ assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
+ }
+
+ @Test
+ fun shadeConfig_dualShadeNotEnabled() = runTest {
+ overrideResource(R.bool.dual_shade_enabled, false)
+ val underTest = create()
+ val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+ assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
+ }
+
+ @Test
+ fun forceCollapseAll() = runTest {
+ val underTest = create()
+ val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
+
+ assertWithMessage("forceCollapseAll should start as false!")
+ .that(forceCollapseAll)
+ .isFalse()
+
+ underTest.setForceCollapseAll(true)
+ assertThat(forceCollapseAll).isTrue()
+
+ underTest.setForceCollapseAll(false)
+ assertThat(forceCollapseAll).isFalse()
+ }
+
+ @Test
+ fun shadeInteraction() = runTest {
+ val underTest = create()
+ val shadeInteraction: MultiShadeInteractionModel? by
+ collectLastValue(underTest.shadeInteraction)
+
+ assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
+
+ underTest.setShadeInteraction(
+ MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
+ )
+ assertThat(shadeInteraction)
+ .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
+
+ underTest.setShadeInteraction(
+ MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
+ )
+ assertThat(shadeInteraction)
+ .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
+
+ underTest.setShadeInteraction(null)
+ assertThat(shadeInteraction).isNull()
+ }
+
+ @Test
+ fun expansion() = runTest {
+ val underTest = create()
+ val leftExpansion: Float? by
+ collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
+ val rightExpansion: Float? by
+ collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
+ val singleExpansion: Float? by
+ collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
+
+ assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
+ assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
+ assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
+
+ underTest.setExpansion(
+ shadeId = ShadeId.LEFT,
+ 0.4f,
+ )
+ assertThat(leftExpansion).isEqualTo(0.4f)
+ assertThat(rightExpansion).isEqualTo(0f)
+ assertThat(singleExpansion).isEqualTo(0f)
+
+ underTest.setExpansion(
+ shadeId = ShadeId.RIGHT,
+ 0.73f,
+ )
+ assertThat(leftExpansion).isEqualTo(0.4f)
+ assertThat(rightExpansion).isEqualTo(0.73f)
+ assertThat(singleExpansion).isEqualTo(0f)
+
+ underTest.setExpansion(
+ shadeId = ShadeId.LEFT,
+ 0.1f,
+ )
+ underTest.setExpansion(
+ shadeId = ShadeId.SINGLE,
+ 0.88f,
+ )
+ assertThat(leftExpansion).isEqualTo(0.1f)
+ assertThat(rightExpansion).isEqualTo(0.73f)
+ assertThat(singleExpansion).isEqualTo(0.88f)
+ }
+
+ private fun create(): MultiShadeRepository {
+ return create(
+ context = context,
+ inputProxy = inputProxy,
+ )
+ }
+
+ companion object {
+ fun create(
+ context: Context,
+ inputProxy: MultiShadeInputProxy,
+ ): MultiShadeRepository {
+ return MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
new file mode 100644
index 0000000..415e68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.domain.interactor
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeInteractorTest : SysuiTestCase() {
+
+ private lateinit var testScope: TestScope
+ private lateinit var inputProxy: MultiShadeInputProxy
+
+ @Before
+ fun setUp() {
+ testScope = TestScope()
+ inputProxy = MultiShadeInputProxy()
+ }
+
+ @Test
+ fun maxShadeExpansion() =
+ testScope.runTest {
+ val underTest = create()
+ val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
+ assertWithMessage("maxShadeExpansion must start with 0.0!")
+ .that(maxShadeExpansion)
+ .isEqualTo(0f)
+
+ underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
+ assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+ underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
+ assertThat(maxShadeExpansion).isEqualTo(0.442f)
+
+ underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
+ assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+ underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
+ assertThat(maxShadeExpansion).isEqualTo(0f)
+ }
+
+ @Test
+ fun isVisible_dualShadeConfig() =
+ testScope.runTest {
+ overrideResource(R.bool.dual_shade_enabled, true)
+ val underTest = create()
+ val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+ val isRightShadeVisible: Boolean? by
+ collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+ val isSingleShadeVisible: Boolean? by
+ collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+ assertThat(isLeftShadeVisible).isTrue()
+ assertThat(isRightShadeVisible).isTrue()
+ assertThat(isSingleShadeVisible).isFalse()
+ }
+
+ @Test
+ fun isVisible_singleShadeConfig() =
+ testScope.runTest {
+ overrideResource(R.bool.dual_shade_enabled, false)
+ val underTest = create()
+ val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+ val isRightShadeVisible: Boolean? by
+ collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+ val isSingleShadeVisible: Boolean? by
+ collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+ assertThat(isLeftShadeVisible).isFalse()
+ assertThat(isRightShadeVisible).isFalse()
+ assertThat(isSingleShadeVisible).isTrue()
+ }
+
+ @Test
+ fun isNonProxiedInputAllowed() =
+ testScope.runTest {
+ val underTest = create()
+ val isLeftShadeNonProxiedInputAllowed: Boolean? by
+ collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
+ assertWithMessage("isNonProxiedInputAllowed should start as true!")
+ .that(isLeftShadeNonProxiedInputAllowed)
+ .isTrue()
+
+ // Need to collect proxied input so the flows become hot as the gesture cancelation code
+ // logic sits in side the proxiedInput flow for each shade.
+ collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
+ collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+
+ // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+ // the
+ // same shade.
+ inputProxy.onProxiedInput(
+ ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+ )
+ assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+ // Registering the end of the proxied interaction re-allows it.
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+ assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+
+ // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+ // disallowing non-proxied input on the LEFT shade.
+ inputProxy.onProxiedInput(
+ ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+ )
+ assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+ // Registering the end of the interaction on the RIGHT shade re-allows it.
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+ assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+ }
+
+ @Test
+ fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+ testScope.runTest {
+ val underTest = create()
+ val isLeftShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+ val isRightShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+ val isSingleShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isLeftShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isRightShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isSingleShadeForceCollapsed)
+ .isFalse()
+
+ // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+ // shade.
+ underTest.onUserInteractionStarted(ShadeId.RIGHT)
+ assertThat(isLeftShadeForceCollapsed).isTrue()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the end of the interaction on the RIGHT shade re-allows it.
+ underTest.onUserInteractionEnded(ShadeId.RIGHT)
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+ // shade.
+ underTest.onUserInteractionStarted(ShadeId.LEFT)
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the end of the interaction on the LEFT shade re-allows it.
+ underTest.onUserInteractionEnded(ShadeId.LEFT)
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+ }
+
+ @Test
+ fun collapseAll() =
+ testScope.runTest {
+ val underTest = create()
+ val isLeftShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+ val isRightShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+ val isSingleShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isLeftShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isRightShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isSingleShadeForceCollapsed)
+ .isFalse()
+
+ underTest.collapseAll()
+ assertThat(isLeftShadeForceCollapsed).isTrue()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isTrue()
+
+ // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
+ // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
+ // interacting with the LEFT shade.
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+ }
+
+ @Test
+ fun onTapOutside_collapsesAll() =
+ testScope.runTest {
+ val underTest = create()
+ val isLeftShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+ val isRightShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+ val isSingleShadeForceCollapsed: Boolean? by
+ collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isLeftShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isRightShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isSingleShadeForceCollapsed)
+ .isFalse()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+ assertThat(isLeftShadeForceCollapsed).isTrue()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isTrue()
+ }
+
+ @Test
+ fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+ testScope.runTest {
+ val underTest = create()
+ val proxiedInput: ProxiedInputModel? by
+ collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+ underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+ assertThat(proxiedInput).isNull()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+ assertThat(proxiedInput).isNull()
+
+ underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+ assertThat(proxiedInput).isNotNull()
+ }
+
+ private fun create(): MultiShadeInteractor {
+ return create(
+ testScope = testScope,
+ context = context,
+ inputProxy = inputProxy,
+ )
+ }
+
+ companion object {
+ fun create(
+ testScope: TestScope,
+ context: Context,
+ inputProxy: MultiShadeInputProxy,
+ ): MultiShadeInteractor {
+ return MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository =
+ MultiShadeRepositoryTest.create(
+ context = context,
+ inputProxy = inputProxy,
+ ),
+ inputProxy = inputProxy,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
new file mode 100644
index 0000000..0484515
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeViewModelTest : SysuiTestCase() {
+
+ private lateinit var testScope: TestScope
+ private lateinit var inputProxy: MultiShadeInputProxy
+
+ @Before
+ fun setUp() {
+ testScope = TestScope()
+ inputProxy = MultiShadeInputProxy()
+ }
+
+ @Test
+ fun scrim_whenDualShadeCollapsed() =
+ testScope.runTest {
+ val alpha = 0.5f
+ overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+ overrideResource(R.bool.dual_shade_enabled, true)
+
+ val underTest = create()
+ val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+ val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+ assertThat(scrimAlpha).isZero()
+ assertThat(isScrimEnabled).isFalse()
+ }
+
+ @Test
+ fun scrim_whenDualShadeExpanded() =
+ testScope.runTest {
+ val alpha = 0.5f
+ overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+ overrideResource(R.bool.dual_shade_enabled, true)
+ val underTest = create()
+ val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+ val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+ assertThat(scrimAlpha).isZero()
+ assertThat(isScrimEnabled).isFalse()
+
+ underTest.leftShade.onExpansionChanged(0.5f)
+ assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
+ assertThat(isScrimEnabled).isTrue()
+
+ underTest.rightShade.onExpansionChanged(1f)
+ assertThat(scrimAlpha).isEqualTo(alpha * 1f)
+ assertThat(isScrimEnabled).isTrue()
+ }
+
+ @Test
+ fun scrim_whenSingleShadeCollapsed() =
+ testScope.runTest {
+ val alpha = 0.5f
+ overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+ overrideResource(R.bool.dual_shade_enabled, false)
+
+ val underTest = create()
+ val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+ val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+ assertThat(scrimAlpha).isZero()
+ assertThat(isScrimEnabled).isFalse()
+ }
+
+ @Test
+ fun scrim_whenSingleShadeExpanded() =
+ testScope.runTest {
+ val alpha = 0.5f
+ overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+ overrideResource(R.bool.dual_shade_enabled, false)
+ val underTest = create()
+ val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+ val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+ underTest.singleShade.onExpansionChanged(0.95f)
+
+ assertThat(scrimAlpha).isZero()
+ assertThat(isScrimEnabled).isFalse()
+ }
+
+ private fun create(): MultiShadeViewModel {
+ return MultiShadeViewModel(
+ viewModelScope = testScope.backgroundScope,
+ interactor =
+ MultiShadeInteractorTest.create(
+ testScope = testScope,
+ context = context,
+ inputProxy = inputProxy,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
new file mode 100644
index 0000000..e32aac5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShadeViewModelTest : SysuiTestCase() {
+
+ private lateinit var testScope: TestScope
+ private lateinit var inputProxy: MultiShadeInputProxy
+ private var interactor: MultiShadeInteractor? = null
+
+ @Before
+ fun setUp() {
+ testScope = TestScope()
+ inputProxy = MultiShadeInputProxy()
+ }
+
+ @Test
+ fun isVisible_dualShadeConfig() =
+ testScope.runTest {
+ overrideResource(R.bool.dual_shade_enabled, true)
+ val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+ val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+ val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+ assertThat(isLeftShadeVisible).isTrue()
+ assertThat(isRightShadeVisible).isTrue()
+ assertThat(isSingleShadeVisible).isFalse()
+ }
+
+ @Test
+ fun isVisible_singleShadeConfig() =
+ testScope.runTest {
+ overrideResource(R.bool.dual_shade_enabled, false)
+ val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+ val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+ val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+ assertThat(isLeftShadeVisible).isFalse()
+ assertThat(isRightShadeVisible).isFalse()
+ assertThat(isSingleShadeVisible).isTrue()
+ }
+
+ @Test
+ fun isSwipingEnabled() =
+ testScope.runTest {
+ val underTest = create(ShadeId.LEFT)
+ val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
+ assertWithMessage("isSwipingEnabled should start as true!")
+ .that(isSwipingEnabled)
+ .isTrue()
+
+ // Need to collect proxied input so the flows become hot as the gesture cancelation code
+ // logic sits in side the proxiedInput flow for each shade.
+ collectLastValue(underTest.proxiedInput)
+ collectLastValue(create(ShadeId.RIGHT).proxiedInput)
+
+ // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+ // the
+ // same shade.
+ inputProxy.onProxiedInput(
+ ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+ )
+ assertThat(isSwipingEnabled).isFalse()
+
+ // Registering the end of the proxied interaction re-allows it.
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+ assertThat(isSwipingEnabled).isTrue()
+
+ // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+ // disallowing non-proxied input on the LEFT shade.
+ inputProxy.onProxiedInput(
+ ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+ )
+ assertThat(isSwipingEnabled).isFalse()
+
+ // Registering the end of the interaction on the RIGHT shade re-allows it.
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+ assertThat(isSwipingEnabled).isTrue()
+ }
+
+ @Test
+ fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+ testScope.runTest {
+ val leftShade = create(ShadeId.LEFT)
+ val rightShade = create(ShadeId.RIGHT)
+ val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
+ val isRightShadeForceCollapsed: Boolean? by
+ collectLastValue(rightShade.isForceCollapsed)
+ val isSingleShadeForceCollapsed: Boolean? by
+ collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isLeftShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isRightShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isSingleShadeForceCollapsed)
+ .isFalse()
+
+ // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+ // shade.
+ rightShade.onDragStarted()
+ assertThat(isLeftShadeForceCollapsed).isTrue()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the end of the interaction on the RIGHT shade re-allows it.
+ rightShade.onDragEnded()
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+ // shade.
+ leftShade.onDragStarted()
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+
+ // Registering the end of the interaction on the LEFT shade re-allows it.
+ leftShade.onDragEnded()
+ assertThat(isLeftShadeForceCollapsed).isFalse()
+ assertThat(isRightShadeForceCollapsed).isFalse()
+ assertThat(isSingleShadeForceCollapsed).isFalse()
+ }
+
+ @Test
+ fun onTapOutside_collapsesAll() =
+ testScope.runTest {
+ val isLeftShadeForceCollapsed: Boolean? by
+ collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
+ val isRightShadeForceCollapsed: Boolean? by
+ collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
+ val isSingleShadeForceCollapsed: Boolean? by
+ collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isLeftShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isRightShadeForceCollapsed)
+ .isFalse()
+ assertWithMessage("isForceCollapsed should start as false!")
+ .that(isSingleShadeForceCollapsed)
+ .isFalse()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+ assertThat(isLeftShadeForceCollapsed).isTrue()
+ assertThat(isRightShadeForceCollapsed).isTrue()
+ assertThat(isSingleShadeForceCollapsed).isTrue()
+ }
+
+ @Test
+ fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+ testScope.runTest {
+ val underTest = create(ShadeId.RIGHT)
+ val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+ underTest.onDragStarted()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+ assertThat(proxiedInput).isNull()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+ assertThat(proxiedInput).isNull()
+
+ underTest.onDragEnded()
+
+ inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+ assertThat(proxiedInput).isNotNull()
+ }
+
+ private fun create(
+ shadeId: ShadeId,
+ ): ShadeViewModel {
+ return ShadeViewModel(
+ viewModelScope = testScope.backgroundScope,
+ shadeId = shadeId,
+ interactor = interactor
+ ?: MultiShadeInteractorTest.create(
+ testScope = testScope,
+ context = context,
+ inputProxy = inputProxy,
+ )
+ .also { interactor = it },
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 3f940d6..40c733a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -232,7 +232,7 @@
verifyZeroInteractions(context)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -366,7 +366,7 @@
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -389,7 +389,7 @@
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 51492eb..bdb0e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -29,12 +29,17 @@
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -48,8 +53,12 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,10 +72,12 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
+
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -100,6 +111,8 @@
private lateinit var underTest: NotificationShadeWindowViewController
+ private lateinit var testScope: TestScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -112,6 +125,13 @@
.thenReturn(keyguardSecurityContainerController)
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow<TransitionStep>())
+
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+ featureFlags.set(Flags.DUAL_SHADE, false)
+
+ val inputProxy = MultiShadeInputProxy()
+ testScope = TestScope()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
@@ -138,6 +158,19 @@
udfpsOverlayInteractor,
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
+ featureFlags,
+ {
+ MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository =
+ MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ ),
+ inputProxy = inputProxy,
+ )
+ },
+ FakeSystemClock(),
)
underTest.setupExpandedStatusBar()
@@ -150,147 +183,162 @@
// tests need to be added to test the rest of handleDispatchTouchEvent.
@Test
- fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
- underTest.setStatusBarViewController(null)
+ fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(null)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- assertThat(returnVal).isFalse()
- }
+ assertThat(returnVal).isFalse()
+ }
@Test
- fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
- whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
+ fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+ whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
- verify(phoneStatusBarViewController).sendTouchToView(ev)
- assertThat(returnVal).isTrue()
- }
+ verify(phoneStatusBarViewController).sendTouchToView(ev)
+ assertThat(returnVal).isTrue()
+ }
@Test
- fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- val downEvBelow =
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
- interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
+ fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ val downEvBelow =
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+ interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
- val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
- whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+ val nextEvent =
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
+ whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
- verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
- assertThat(returnVal).isTrue()
- }
+ verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+ assertThat(returnVal).isTrue()
+ }
@Test
- fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
+ fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- verify(phoneStatusBarViewController).sendTouchToView(downEv)
- assertThat(returnVal).isTrue()
- }
+ verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT)
+ assertThat(returnVal).isTrue()
+ }
@Test
- fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- // Item we're testing
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+ fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ // Item we're testing
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isNull()
- }
+ verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+ assertThat(returnVal).isNull()
+ }
@Test
- fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
- // Item we're testing
- whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(false)
+ fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ // Item we're testing
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(false)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isNull()
- }
+ verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+ assertThat(returnVal).isNull()
+ }
@Test
- fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- // Item we're testing
- whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
+ fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ // Item we're testing
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isTrue()
- }
+ verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+ assertThat(returnVal).isTrue()
+ }
@Test
- fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
+ fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() =
+ testScope.runTest {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
- // Down event first
- interactionEventHandler.handleDispatchTouchEvent(downEv)
+ // Down event first
+ interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
- // Then another event
- val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
- whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+ // Then another event
+ val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
- val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
- verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
- assertThat(returnVal).isTrue()
- }
+ verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+ assertThat(returnVal).isTrue()
+ }
@Test
- fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() {
- // Down event within udfpsOverlay bounds while alternateBouncer is showing
- whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false)
- whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+ fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
+ testScope.runTest {
+ // Down event within udfpsOverlay bounds while alternateBouncer is showing
+ whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
+ .thenReturn(false)
+ whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
- // Then touch should not be intercepted
- val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv)
- assertThat(shouldIntercept).isFalse()
- }
+ // Then touch should not be intercepted
+ val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+ assertThat(shouldIntercept).isFalse()
+ }
@Test
- fun testGetBouncerContainer() {
- Mockito.clearInvocations(view)
- underTest.bouncerContainer
- verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
- }
+ fun testGetBouncerContainer() =
+ testScope.runTest {
+ Mockito.clearInvocations(view)
+ underTest.bouncerContainer
+ verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
+ }
@Test
- fun testGetKeyguardMessageArea() {
- underTest.keyguardMessageArea
- verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+ fun testGetKeyguardMessageArea() =
+ testScope.runTest {
+ underTest.keyguardMessageArea
+ verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+ }
+
+ companion object {
+ private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ private const val VIEW_BOTTOM = 100
}
}
-
-private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
deleted file mode 100644
index 2f528a8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2017 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.shade;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
-import android.os.SystemClock;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.ViewGroup;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardSecurityContainerController;
-import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.statusbar.DragDownHelper;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationInsetsController;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.tuner.TunerService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class NotificationShadeWindowViewTest extends SysuiTestCase {
-
- private NotificationShadeWindowView mView;
- private NotificationShadeWindowViewController mController;
-
- @Mock private TunerService mTunerService;
- @Mock private DragDownHelper mDragDownHelper;
- @Mock private SysuiStatusBarStateController mStatusBarStateController;
- @Mock private ShadeController mShadeController;
- @Mock private CentralSurfaces mCentralSurfaces;
- @Mock private DockManager mDockManager;
- @Mock private NotificationPanelViewController mNotificationPanelViewController;
- @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
- @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
- @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
- @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock private LockIconViewController mLockIconViewController;
- @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
- @Mock private AmbientState mAmbientState;
- @Mock private PulsingGestureListener mPulsingGestureListener;
- @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
- @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
- @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
- @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
- @Mock private NotificationInsetsController mNotificationInsetsController;
- @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
- @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
- @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
-
- @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
- mInteractionEventHandlerCaptor;
- private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mView = spy(new NotificationShadeWindowView(getContext(), null));
- when(mView.findViewById(R.id.notification_stack_scroller))
- .thenReturn(mNotificationStackScrollLayout);
-
- when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
- when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
- mKeyguardBouncerComponent);
- when(mKeyguardBouncerComponent.getSecurityContainerController()).thenReturn(
- mKeyguardSecurityContainerController);
-
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- mDependency.injectTestDependency(ShadeController.class, mShadeController);
-
- when(mDockManager.isDocked()).thenReturn(false);
-
- when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
- .thenReturn(emptyFlow());
-
- mController = new NotificationShadeWindowViewController(
- mLockscreenShadeTransitionController,
- new FalsingCollectorFake(),
- mStatusBarStateController,
- mDockManager,
- mNotificationShadeDepthController,
- mView,
- mNotificationPanelViewController,
- new ShadeExpansionStateManager(),
- mNotificationStackScrollLayoutController,
- mStatusBarKeyguardViewManager,
- mStatusBarWindowStateController,
- mLockIconViewController,
- mCentralSurfaces,
- mNotificationShadeWindowController,
- mKeyguardUnlockAnimationController,
- mNotificationInsetsController,
- mAmbientState,
- mPulsingGestureListener,
- mKeyguardBouncerViewModel,
- mKeyguardBouncerComponentFactory,
- mAlternateBouncerInteractor,
- mUdfpsOverlayInteractor,
- mKeyguardTransitionInteractor,
- mPrimaryBouncerToGoneTransitionViewModel
- );
- mController.setupExpandedStatusBar();
- mController.setDragDownHelper(mDragDownHelper);
- }
-
- @Test
- public void testDragDownHelperCalledWhenDraggingDown() {
- when(mDragDownHelper.isDraggingDown()).thenReturn(true);
- long now = SystemClock.elapsedRealtime();
- MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
- 0 /* meta */);
- mView.onTouchEvent(ev);
- verify(mDragDownHelper).onTouchEvent(ev);
- ev.recycle();
- }
-
- @Test
- public void testInterceptTouchWhenShowingAltAuth() {
- captureInteractionEventHandler();
-
- // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
- when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true);
- when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
- // THEN we should intercept touch
- assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
- }
-
- @Test
- public void testNoInterceptTouch() {
- captureInteractionEventHandler();
-
- // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
- when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
- // THEN we shouldn't intercept touch
- assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
- }
-
- @Test
- public void testHandleTouchEventWhenShowingAltAuth() {
- captureInteractionEventHandler();
-
- // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
- when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
- // THEN we should handle the touch
- assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
- }
-
- private void captureInteractionEventHandler() {
- verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
- mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
-
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
new file mode 100644
index 0000000..5d0f408
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 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.shade
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityContainerController
+import com.android.keyguard.LockIconViewController
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.DragDownHelper
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
+import com.android.systemui.statusbar.NotificationShadeDepthController
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationShadeWindowViewTest : SysuiTestCase() {
+
+ @Mock private lateinit var dragDownHelper: DragDownHelper
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dockManager: DockManager
+ @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
+ @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock
+ private lateinit var notificationStackScrollLayoutController:
+ NotificationStackScrollLayoutController
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+ @Mock
+ private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+ @Mock private lateinit var lockIconViewController: LockIconViewController
+ @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+ @Mock private lateinit var ambientState: AmbientState
+ @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+ @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
+ @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
+ @Mock
+ private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
+ @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock
+ private lateinit var primaryBouncerToGoneTransitionViewModel:
+ PrimaryBouncerToGoneTransitionViewModel
+ @Captor
+ private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+ @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+
+ private lateinit var underTest: NotificationShadeWindowView
+ private lateinit var controller: NotificationShadeWindowViewController
+ private lateinit var interactionEventHandler: InteractionEventHandler
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = spy(NotificationShadeWindowView(context, null))
+ whenever(
+ underTest.findViewById<NotificationStackScrollLayout>(
+ R.id.notification_stack_scroller
+ )
+ )
+ .thenReturn(notificationStackScrollLayout)
+ whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
+ .thenReturn(mock())
+ whenever(keyguardBouncerComponentFactory.create(any())).thenReturn(keyguardBouncerComponent)
+ whenever(keyguardBouncerComponent.securityContainerController)
+ .thenReturn(keyguardSecurityContainerController)
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ mDependency.injectTestDependency(ShadeController::class.java, shadeController)
+ whenever(dockManager.isDocked).thenReturn(false)
+ whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+ .thenReturn(emptyFlow())
+
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+ featureFlags.set(Flags.DUAL_SHADE, false)
+ val inputProxy = MultiShadeInputProxy()
+ testScope = TestScope()
+ controller =
+ NotificationShadeWindowViewController(
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ statusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ underTest,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ notificationStackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ notificationShadeWindowController,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ pulsingGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ alternateBouncerInteractor,
+ udfpsOverlayInteractor,
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ featureFlags,
+ {
+ MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository =
+ MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ ),
+ inputProxy = inputProxy,
+ )
+ },
+ FakeSystemClock(),
+ )
+
+ controller.setupExpandedStatusBar()
+ controller.setDragDownHelper(dragDownHelper)
+ }
+
+ @Test
+ fun testDragDownHelperCalledWhenDraggingDown() =
+ testScope.runTest {
+ whenever(dragDownHelper.isDraggingDown).thenReturn(true)
+ val now = SystemClock.elapsedRealtime()
+ val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
+ underTest.onTouchEvent(ev)
+ verify(dragDownHelper).onTouchEvent(ev)
+ ev.recycle()
+ }
+
+ @Test
+ fun testInterceptTouchWhenShowingAltAuth() =
+ testScope.runTest {
+ captureInteractionEventHandler()
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+ whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true)
+ whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+ // THEN we should intercept touch
+ assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue()
+ }
+
+ @Test
+ fun testNoInterceptTouch() =
+ testScope.runTest {
+ captureInteractionEventHandler()
+
+ // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false)
+ whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+ // THEN we shouldn't intercept touch
+ assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse()
+ }
+
+ @Test
+ fun testHandleTouchEventWhenShowingAltAuth() =
+ testScope.runTest {
+ captureInteractionEventHandler()
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+ whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+ // THEN we should handle the touch
+ assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue()
+ }
+
+ private fun captureInteractionEventHandler() {
+ verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
+ interactionEventHandler = interactionEventHandlerCaptor.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64e58d0..0e2a3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -134,7 +134,7 @@
TransitionInfoBuilder(@WindowManager.TransitionType int type) {
mInfo = new TransitionInfo(type, 0 /* flags */);
- mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+ mInfo.addRootLeash(0, createMockSurface(true /* valid */), 0, 0);
}
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -144,6 +144,7 @@
change.setMode(mode);
change.setFlags(flags);
change.setTaskInfo(taskInfo);
+ change.setDisplayId(0, 0);
mInfo.addChange(change);
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index a280510..58b44ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -24,6 +24,7 @@
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dreams.smartspace.DreamSmartspaceController
@@ -46,6 +47,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
import org.mockito.Spy
@@ -69,12 +71,21 @@
private lateinit var viewComponent: SmartspaceViewComponent
@Mock
+ private lateinit var weatherViewComponent: SmartspaceViewComponent
+
+ @Spy
+ private var weatherSmartspaceView: SmartspaceView = TestView(context)
+
+ @Mock
private lateinit var targetFilter: SmartspaceTargetFilter
@Mock
private lateinit var plugin: BcSmartspaceDataPlugin
@Mock
+ private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+ @Mock
private lateinit var precondition: SmartspacePrecondition
@Spy
@@ -88,6 +99,9 @@
private lateinit var controller: DreamSmartspaceController
+ // TODO(b/272811280): Remove usage of real view
+ private val fakeParent = FrameLayout(context)
+
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
* object inheritance and interface implementation used in DreamSmartspaceController
@@ -121,13 +135,17 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- `when`(viewComponentFactory.create(any(), eq(plugin), any()))
+ `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
.thenReturn(viewComponent)
`when`(viewComponent.getView()).thenReturn(smartspaceView)
+ `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
+ .thenReturn(weatherViewComponent)
+ `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
- viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin))
+ viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
+ Optional.of(weatherPlugin))
}
/**
@@ -168,11 +186,11 @@
`when`(precondition.conditionsMet()).thenReturn(true)
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
- var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
- verify(viewComponentFactory).create(any(), eq(plugin), capture())
+ val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
+ verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
}
- var mockView = Mockito.mock(TestView::class.java)
+ val mockView = Mockito.mock(TestView::class.java)
`when`(precondition.conditionsMet()).thenReturn(true)
stateChangeListener.onViewAttachedToWindow(mockView)
@@ -183,4 +201,74 @@
verify(session).close()
}
+
+ /**
+ * Ensures session is created when weather smartspace view is created and attached.
+ */
+ @Test
+ fun testConnectOnWeatherViewCreate() {
+ `when`(precondition.conditionsMet()).thenReturn(true)
+
+ val customView = Mockito.mock(TestView::class.java)
+ val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+ val weatherSmartspaceView = weatherView as SmartspaceView
+ fakeParent.addView(weatherView)
+
+ // Then weather view is created with custom view and the default weatherPlugin.getView
+ // should not be called
+ verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
+ eq(customView))
+ verify(weatherPlugin, Mockito.never()).getView(fakeParent)
+
+ // And then session is created
+ controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+ verify(smartspaceManager).createSmartspaceSession(any())
+ verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+ verify(weatherSmartspaceView).setDozeAmount(0f)
+ }
+
+ /**
+ * Ensures weather plugin registers target listener when it is added from the controller.
+ */
+ @Test
+ fun testAddListenerInController_registersListenerForWeatherPlugin() {
+ val customView = Mockito.mock(TestView::class.java)
+ `when`(precondition.conditionsMet()).thenReturn(true)
+
+ // Given a session is created
+ val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+ controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+ verify(smartspaceManager).createSmartspaceSession(any())
+
+ // When a listener is added
+ controller.addListenerForWeatherPlugin(listener)
+
+ // Then the listener is registered to the weather plugin only
+ verify(weatherPlugin).registerListener(listener)
+ verify(plugin, Mockito.never()).registerListener(any())
+ }
+
+ /**
+ * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+ * view is detached.
+ */
+ @Test
+ fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+ `when`(precondition.conditionsMet()).thenReturn(true)
+
+ // Given a session is created
+ val customView = Mockito.mock(TestView::class.java)
+ val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+ controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+ verify(smartspaceManager).createSmartspaceSession(any())
+
+ // When view is detached
+ controller.stateChangeListener.onViewDetachedFromWindow(weatherView)
+ // Then the session is closed
+ verify(session).close()
+
+ // And the listener receives an empty list of targets and unregisters the notifier
+ verify(weatherPlugin).onTargetsAvailable(emptyList())
+ verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 5170678..ced0734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -34,6 +34,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -41,6 +42,8 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,8 +52,6 @@
import java.util.Optional;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -84,6 +85,7 @@
() -> Optional.of(mock(CentralSurfaces.class)),
mStateController,
mRemoteInputUriController,
+ mock(RemoteInputControllerLogger.class),
mClickNotifier,
mock(ActionClickLogger.class),
mock(DumpManager.class));
@@ -141,6 +143,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
StatusBarStateController statusBarStateController,
RemoteInputUriController remoteInputUriController,
+ RemoteInputControllerLogger remoteInputControllerLogger,
NotificationClickNotifier clickNotifier,
ActionClickLogger actionClickLogger,
DumpManager dumpManager) {
@@ -153,6 +156,7 @@
centralSurfacesOptionalLazy,
statusBarStateController,
remoteInputUriController,
+ remoteInputControllerLogger,
clickNotifier,
actionClickLogger,
dumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e6f272b..3327e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -167,4 +167,13 @@
controller.setIsDreaming(false)
verify(listener).onDreamingChanged(false)
}
+
+ @Test
+ fun testSetDreamState_getterReturnsCurrentState() {
+ controller.setIsDreaming(true)
+ assertTrue(controller.isDreaming())
+
+ controller.setIsDreaming(false)
+ assertFalse(controller.isDreaming())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index cb4f119..4bb14a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -58,6 +59,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.BDDMockito.clearInvocations
import org.mockito.BDDMockito.given
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -166,6 +168,12 @@
mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
+
+ // Set the default FSI decision
+ setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+
+ // Run tests with default feature flag state
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default)
}
@Test
@@ -810,6 +818,39 @@
}
@Test
+ fun onEntryAdded_whenLaunchingFSI_doesLogDecision() {
+ // GIVEN A new notification can FSI
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+ mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ }
+
+ @Test
+ fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() {
+ // GIVEN A new notification can't FSI
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+ mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+ }
+
+ @Test
+ fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() {
+ // GIVEN A new notification can't FSI because of DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+ mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ }
+
+ @Test
fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
// Ensure the feature flag is off
whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
@@ -818,13 +859,22 @@
setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
mCollectionListener.onEntryAdded(mEntry)
+ // Verify that this causes a log
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+ mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ clearInvocations(mNotificationInterruptStateProvider)
+
// and it is then updated to allow full screen
setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
mCollectionListener.onRankingApplied()
// THEN it should not full screen because the feature is off
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+
+ // VERIFY that no additional logging happens either
+ verify(mNotificationInterruptStateProvider, never())
+ .logFullScreenIntentDecision(any(), any())
}
@Test
@@ -836,8 +886,11 @@
setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
mCollectionListener.onEntryAdded(mEntry)
- // at this point, it should not have full screened
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ // at this point, it should not have full screened, but should have logged
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ clearInvocations(mNotificationInterruptStateProvider)
// and it is then updated to allow full screen AND HUN
setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
@@ -847,10 +900,110 @@
mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
- // THEN it should full screen but it should NOT HUN
+ // THEN it should full screen and log but it should NOT HUN
verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
verify(mHeadsUpManager, never()).showNotification(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+ FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ clearInvocations(mNotificationInterruptStateProvider)
+
+ // WHEN ranking updates again and the pipeline reruns
+ clearInvocations(mLaunchFullScreenIntentProvider)
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // VERIFY that the FSI does not launch again or log
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mNotificationInterruptStateProvider, never())
+ .logFullScreenIntentDecision(any(), any())
+ }
+
+ @Test
+ fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() {
+ // Turn on the feature
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+ // GIVEN that mEntry was previously suppressed from full-screen only by DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // at this point, it should not have full screened, but should have logged
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ clearInvocations(mNotificationInterruptStateProvider)
+
+ // ranking is applied with only DND blocking FSI
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN it should still not yet full screen or HUN
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+
+ // Same decision as before; is not logged
+ verify(mNotificationInterruptStateProvider, never())
+ .logFullScreenIntentDecision(any(), any())
+ clearInvocations(mNotificationInterruptStateProvider)
+
+ // and it is then updated to allow full screen AND HUN
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN it should full screen and log but it should NOT HUN
+ verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+ verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+ FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ clearInvocations(mNotificationInterruptStateProvider)
+ }
+
+ @Test
+ fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() {
+ // Turn on the feature
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+ // GIVEN that mEntry was previously suppressed from full-screen only by DND
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // at this point, it should not have full screened
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+ // now some other condition blocks FSI in addition to DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN it should NOT full screen or HUN
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+
+ // NOW the DND logic changes and FSI and HUN are available
+ clearInvocations(mLaunchFullScreenIntentProvider)
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(mEntry)
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // VERIFY that the FSI didn't happen, but that we do HUN
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ finishBind(mEntry)
+ verify(mHeadsUpManager).showNotification(mEntry)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 07d0dbd..653b0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -36,9 +36,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -51,7 +54,6 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.service.dreams.IDreamManager;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -76,6 +78,10 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Tests for the interruption state provider which understands whether the system & notification
* is in a state allowing a particular notification to hun, pulse, or bubble.
@@ -87,8 +93,6 @@
@Mock
PowerManager mPowerManager;
@Mock
- IDreamManager mDreamManager;
- @Mock
AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock
StatusBarStateController mStatusBarStateController;
@@ -126,7 +130,6 @@
new NotificationInterruptStateProviderImpl(
mContext.getContentResolver(),
mPowerManager,
- mDreamManager,
mAmbientDisplayConfiguration,
mBatteryController,
mStatusBarStateController,
@@ -150,7 +153,7 @@
when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mPowerManager.isScreenOn()).thenReturn(true);
}
@@ -352,7 +355,7 @@
// Also not in use if screen is on but we're showing screen saver / "dreaming"
when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
@@ -532,7 +535,7 @@
public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
when(mPowerManager.isInteractive()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -551,7 +554,7 @@
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
.build();
when(mPowerManager.isInteractive()).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -560,7 +563,7 @@
.isFalse();
verify(mLogger, never()).logFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_ONLY_BY_DND");
}
@Test
@@ -570,7 +573,7 @@
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
.build();
when(mPowerManager.isInteractive()).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -579,7 +582,7 @@
.isFalse();
verify(mLogger, never()).logFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_BY_DND");
}
@Test
@@ -592,14 +595,14 @@
public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger).logNoFullscreen(entry, "Not important enough");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_NOT_IMPORTANT_ENOUGH");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
}
@@ -614,7 +617,7 @@
public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
when(mPowerManager.isInteractive()).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -622,7 +625,8 @@
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger, never()).logNoFullscreen(any(), any());
- verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+ verify(mLogger).logNoFullscreenWarning(entry,
+ "NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: GroupAlertBehavior will prevent HUN");
verify(mLogger, never()).logFullscreen(any(), any());
assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
@@ -643,7 +647,7 @@
public void testShouldFullScreen_notInteractive() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -652,7 +656,7 @@
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Device is not interactive");
+ verify(mLogger).logFullscreen(entry, "FSI_DEVICE_NOT_INTERACTIVE");
}
@Test
@@ -665,7 +669,7 @@
public void testShouldFullScreen_isDreaming() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -674,7 +678,7 @@
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Device is dreaming");
+ verify(mLogger).logFullscreen(entry, "FSI_DEVICE_IS_DREAMING");
}
@Test
@@ -687,7 +691,7 @@
public void testShouldFullScreen_onKeyguard() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -696,7 +700,7 @@
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+ verify(mLogger).logFullscreen(entry, "FSI_KEYGUARD_SHOWING");
}
@Test
@@ -710,14 +714,14 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
}
@@ -727,7 +731,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
@@ -737,7 +741,7 @@
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+ verify(mLogger).logFullscreen(entry, "FSI_EXPECTED_NOT_TO_HUN");
}
@Test
@@ -746,7 +750,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -756,7 +760,7 @@
.isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
}
@@ -767,7 +771,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -792,7 +796,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -802,7 +806,7 @@
.isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
}
@@ -813,7 +817,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -838,7 +842,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(false);
@@ -848,18 +852,43 @@
.isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
}
@Test
+ public void logFullScreenIntentDecision_shouldAlmostAlwaysLogOneTime() {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
+ FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+ FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
+ ));
+ for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
+ clearInvocations(mLogger);
+ boolean expectedToLog = decision != FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
+ boolean isWarning = warnings.contains(decision);
+ mNotifInterruptionStateProvider.logFullScreenIntentDecision(entry, decision);
+ if (decision.shouldLaunch) {
+ verify(mLogger).logFullscreen(eq(entry), contains(decision.name()));
+ } else if (expectedToLog) {
+ if (isWarning) {
+ verify(mLogger).logNoFullscreenWarning(eq(entry), contains(decision.name()));
+ } else {
+ verify(mLogger).logNoFullscreen(eq(entry), contains(decision.name()));
+ }
+ }
+ verifyNoMoreInteractions(mLogger);
+ }
+ }
+
+ @Test
public void testShouldHeadsUp_snoozed_unlocked_withStrictRules() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 031c17f..7db2197 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -347,7 +347,6 @@
mNotificationInterruptStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mPowerManager,
- mDreamManager,
mAmbientDisplayConfiguration,
mStatusBarStateController,
mKeyguardStateController,
@@ -730,7 +729,7 @@
public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
.setGroup("a")
@@ -753,7 +752,7 @@
public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
.setGroup("a")
@@ -776,7 +775,7 @@
public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -797,7 +796,7 @@
public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -1400,7 +1399,6 @@
TestableNotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
PowerManager powerManager,
- IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
StatusBarStateController controller,
KeyguardStateController keyguardStateController,
@@ -1415,7 +1413,6 @@
super(
contentResolver,
powerManager,
- dreamManager,
ambientDisplayConfiguration,
batteryController,
controller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index f6e5959..542b688 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -18,17 +18,16 @@
import android.content.Intent
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CellSignalStrengthCdma
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
-import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
@@ -68,6 +67,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -75,14 +75,12 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -99,7 +97,6 @@
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
- private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
private val systemUiCarrierConfig =
SystemUiCarrierConfig(
@@ -107,6 +104,9 @@
createTestConfig(),
)
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -124,21 +124,16 @@
systemUiCarrierConfig,
fakeBroadcastDispatcher,
mobileMappings,
- IMMEDIATE,
+ testDispatcher,
logger,
tableLogger,
- scope,
+ testScope.backgroundScope,
)
}
- @After
- fun tearDown() {
- scope.cancel()
- }
-
@Test
fun emergencyOnly() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
@@ -154,18 +149,15 @@
@Test
fun emergencyOnly_toggles() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
- val serviceState = ServiceState()
- serviceState.isEmergencyOnly = true
- callback.onServiceStateChanged(serviceState)
+ callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = true })
assertThat(latest).isTrue()
- serviceState.isEmergencyOnly = false
- callback.onServiceStateChanged(serviceState)
+ callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = false })
assertThat(latest).isFalse()
@@ -174,7 +166,7 @@
@Test
fun cdmaLevelUpdates() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Int? = null
val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
@@ -194,7 +186,7 @@
@Test
fun gsmLevelUpdates() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Int? = null
val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
@@ -214,7 +206,7 @@
@Test
fun isGsm() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
@@ -234,7 +226,7 @@
@Test
fun dataConnectionState_connected() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -249,7 +241,7 @@
@Test
fun dataConnectionState_connecting() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -264,7 +256,7 @@
@Test
fun dataConnectionState_disconnected() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -279,7 +271,7 @@
@Test
fun dataConnectionState_disconnecting() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -294,7 +286,7 @@
@Test
fun dataConnectionState_suspended() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -309,7 +301,7 @@
@Test
fun dataConnectionState_handoverInProgress() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -324,7 +316,7 @@
@Test
fun dataConnectionState_unknown() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -339,7 +331,7 @@
@Test
fun dataConnectionState_invalid() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -354,7 +346,7 @@
@Test
fun dataActivity() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
@@ -368,7 +360,7 @@
@Test
fun carrierNetworkChange() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
@@ -382,7 +374,7 @@
@Test
fun networkType_default() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -395,7 +387,7 @@
@Test
fun networkType_unknown_hasCorrectKey() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -413,14 +405,19 @@
@Test
fun networkType_updatesUsingDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val overrideType = OVERRIDE_NETWORK_TYPE_NONE
val type = NETWORK_TYPE_LTE
val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
- val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ val ti =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.overrideNetworkType).thenReturn(overrideType)
+ whenever(it.networkType).thenReturn(type)
+ }
callback.onDisplayInfoChanged(ti)
assertThat(latest).isEqualTo(expected)
@@ -430,7 +427,7 @@
@Test
fun networkType_updatesUsingOverride() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -450,16 +447,38 @@
}
@Test
+ fun networkType_unknownNetworkWithOverride_usesOverrideKey() =
+ testScope.runTest {
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val unknown = NETWORK_TYPE_UNKNOWN
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
+ val ti =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.networkType).thenReturn(unknown)
+ whenever(it.overrideNetworkType).thenReturn(type)
+ }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
fun dataEnabled_initial_false() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
assertThat(underTest.dataEnabled.value).isFalse()
}
@Test
- fun `is data enabled - tracks telephony callback`() =
- runBlocking(IMMEDIATE) {
+ fun isDataEnabled_tracksTelephonyCallback() =
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
@@ -479,7 +498,7 @@
@Test
fun numberOfLevels_isDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
@@ -489,51 +508,68 @@
}
@Test
- fun `roaming - cdma - queries telephony manager`() =
- runBlocking(IMMEDIATE) {
+ fun roaming_cdma_queriesTelephonyManager() =
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val cb = getTelephonyCallbackForType<ServiceStateListener>()
- val serviceState = ServiceState()
- serviceState.roaming = false
-
- // CDMA roaming is off, GSM roaming is off
+ // CDMA roaming is off, GSM roaming is on
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
- cb.onServiceStateChanged(serviceState)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
assertThat(latest).isFalse()
- // CDMA roaming is off, GSM roaming is on
+ // CDMA roaming is on, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
- cb.onServiceStateChanged(serviceState)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
assertThat(latest).isTrue()
job.cancel()
}
+ /**
+ * [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
+ * not running or if there is an error while retrieving the cdma ERI
+ */
@Test
- fun `roaming - gsm - queries service state`() =
- runBlocking(IMMEDIATE) {
+ fun cdmaRoaming_ignoresNegativeOne() =
+ testScope.runTest {
var latest: Boolean? = null
- val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+ val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.roaming = false
val cb = getTelephonyCallbackForType<ServiceStateListener>()
- // CDMA roaming is off, GSM roaming is off
- whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ // CDMA roaming is unavailable (-1), GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
cb.onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun roaming_gsm_queriesServiceState() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+ assertThat(latest).isFalse()
+
// CDMA roaming is off, GSM roaming is on
- serviceState.roaming = true
- cb.onServiceStateChanged(serviceState)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
assertThat(latest).isTrue()
@@ -541,8 +577,8 @@
}
@Test
- fun `activity - updates from callback`() =
- runBlocking(IMMEDIATE) {
+ fun activity_updatesFromCallback() =
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
@@ -578,8 +614,8 @@
}
@Test
- fun `network name - default`() =
- runBlocking(IMMEDIATE) {
+ fun networkName_default() =
+ testScope.runTest {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -589,8 +625,8 @@
}
@Test
- fun `network name - uses broadcast info - returns derived`() =
- runBlocking(IMMEDIATE) {
+ fun networkName_usesBroadcastInfo_returnsDerived() =
+ testScope.runTest {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -606,8 +642,8 @@
}
@Test
- fun `network name - broadcast not for this sub id - keeps old value`() =
- runBlocking(IMMEDIATE) {
+ fun networkName_broadcastNotForThisSubId_keepsOldValue() =
+ testScope.runTest {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -631,8 +667,8 @@
}
@Test
- fun `network name - broadcast has no data - updates to default`() =
- runBlocking(IMMEDIATE) {
+ fun networkName_broadcastHasNoData_updatesToDefault() =
+ testScope.runTest {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -658,8 +694,8 @@
}
@Test
- fun `operatorAlphaShort - tracked`() =
- runBlocking(IMMEDIATE) {
+ fun operatorAlphaShort_tracked() =
+ testScope.runTest {
var latest: String? = null
val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
@@ -680,33 +716,45 @@
}
@Test
- fun `connection model - isInService - not iwlan`() =
- runBlocking(IMMEDIATE) {
+ fun isInService_notIwlan() =
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
- val serviceState = ServiceState()
- serviceState.voiceRegState = STATE_IN_SERVICE
- serviceState.dataRegState = STATE_IN_SERVICE
-
- getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_IN_SERVICE
+ it.dataRegState = STATE_IN_SERVICE
+ }
+ )
assertThat(latest).isTrue()
- serviceState.voiceRegState = STATE_OUT_OF_SERVICE
- getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.dataRegState = STATE_IN_SERVICE
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ }
+ )
assertThat(latest).isTrue()
- serviceState.dataRegState = STATE_OUT_OF_SERVICE
- getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.dataRegState = STATE_OUT_OF_SERVICE
+ }
+ )
assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
- runBlocking(IMMEDIATE) {
+ fun isInService_isIwlan_voiceOutOfService_dataInService() =
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
@@ -730,8 +778,8 @@
}
@Test
- fun `number of levels - uses carrier config`() =
- runBlocking(IMMEDIATE) {
+ fun numberOfLevels_usesCarrierConfig() =
+ testScope.runTest {
var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
@@ -756,19 +804,6 @@
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
- /** Convenience constructor for SignalStrength */
- private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
- val signalStrength = mock<SignalStrength>()
- whenever(signalStrength.isGsm).thenReturn(isGsm)
- whenever(signalStrength.level).thenReturn(gsmLevel)
- val cdmaStrength =
- mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
- whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
- .thenReturn(listOf(cdmaStrength))
-
- return signalStrength
- }
-
private fun spnIntent(
subId: Int = SUB_1_ID,
showSpn: Boolean = true,
@@ -785,7 +820,6 @@
}
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
new file mode 100644
index 0000000..bbf04ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.ServiceState
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DataEnabledListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * Test class to stress test the TelephonyCallbacks that we listen to. In particular, the callbacks
+ * all come back in on a single listener (for reasons defined in the system). This test is built to
+ * ensure that we don't miss any important callbacks.
+ *
+ * Kind of like an interaction test case build just for [TelephonyCallback]
+ *
+ * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
+ * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
+ * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
+ * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ *
+ * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
+ * by only a single callback can immediately create backpressure on the other fields related to a
+ * mobile connection.
+ *
+ * This test should be designed to test _at least_ each individual callback in a smoke-test fashion.
+ * The way we will achieve this is as follows:
+ * 1. Start up a listener (A) collecting on a field which is _not under test_
+ * 2. Send a single event to a telephony callback which supports the field under test (B)
+ * 3. Send many (may be as few as 2) events to the callback backing A to ensure we start seeing
+ * backpressure on other fields NOTE: poor handling of backpressure here would normally cause B
+ * to get dropped
+ * 4. Start up a new collector for B
+ * 5. Assert that B has the state sent in step #2
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionRepositoryImpl
+ private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: MobileInputLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
+
+ private val mobileMappings = FakeMobileMappingsProxy()
+ private val systemUiCarrierConfig =
+ SystemUiCarrierConfig(
+ SUB_1_ID,
+ SystemUiCarrierConfigTest.createTestConfig(),
+ )
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+
+ connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
+ underTest =
+ MobileConnectionRepositoryImpl(
+ context,
+ SUB_1_ID,
+ DEFAULT_NAME,
+ SEP,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappings,
+ testDispatcher,
+ logger,
+ tableLogger,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun carrierNetworkChangeListener_noisyActivity() =
+ testScope.runTest {
+ var latest: Boolean? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ flipActivity(100, activityCallback)
+
+ val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ activityJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun dataActivityLate_noisyDisplayInfo() =
+ testScope.runTest {
+ var latest: DataActivityModel? = null
+
+ // start collecting displayInfo; don't care about the result
+ val displayInfoJob = underTest.resolvedNetworkType.launchIn(this)
+
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+ activityCallback.onDataActivity(DATA_ACTIVITY_INOUT)
+
+ val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val type1 = NETWORK_TYPE_UNKNOWN
+ val type2 = NETWORK_TYPE_LTE
+ val t1 =
+ mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type1) }
+ val t2 =
+ mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type2) }
+
+ flipDisplayInfo(100, listOf(t1, t2), displayInfoCallback)
+
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest)
+ .isEqualTo(
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = true,
+ )
+ )
+
+ displayInfoJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun dataConnectionStateListener_noisyActivity() =
+ testScope.runTest {
+ var latest: DataConnectionState? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+ val connectionCallback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ connectionCallback.onDataConnectionStateChanged(
+ TelephonyManager.DATA_CONNECTED,
+ 200 /* unused */
+ )
+
+ // Send a bunch of events that we don't care about, to overrun the replay buffer
+ flipActivity(100, activityCallback)
+
+ val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
+
+ activityJob.cancel()
+ connectionJob.cancel()
+ }
+
+ @Test
+ fun dataEnabledLate_noisyActivity() =
+ testScope.runTest {
+ var latest: Boolean? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+ val enabledCallback = getTelephonyCallbackForType<DataEnabledListener>()
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
+
+ // Send a bunch of events that we don't care about, to overrun the replay buffer
+ flipActivity(100, activityCallback)
+
+ val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ activityJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun displayInfoLate_noisyActivity() =
+ testScope.runTest {
+ var latest: ResolvedNetworkType? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+ val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ val type = NETWORK_TYPE_LTE
+ val expected = ResolvedNetworkType.DefaultNetworkType(mobileMappings.toIconKey(type))
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ displayInfoCallback.onDisplayInfoChanged(ti)
+
+ // Send a bunch of events that we don't care about, to overrun the replay buffer
+ flipActivity(100, activityCallback)
+
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ activityJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun serviceStateListener_noisyActivity() =
+ testScope.runTest {
+ var latest: Boolean? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+ val serviceStateCallback = getTelephonyCallbackForType<ServiceStateListener>()
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ // isEmergencyOnly comes in
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ serviceStateCallback.onServiceStateChanged(serviceState)
+
+ flipActivity(100, activityCallback)
+
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ activityJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun signalStrengthsListenerLate_noisyActivity() =
+ testScope.runTest {
+ var latest: Int? = null
+
+ // Start collecting data activity; don't care about the result
+ val activityJob = underTest.dataActivityDirection.launchIn(this)
+ val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ flipActivity(100, activityCallback)
+
+ val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(2)
+
+ activityJob.cancel()
+ job.cancel()
+ }
+
+ private fun flipActivity(
+ times: Int,
+ callback: DataActivityListener,
+ ) {
+ repeat(times) { index -> callback.onDataActivity(index % 4) }
+ }
+
+ private fun flipDisplayInfo(
+ times: Int,
+ infos: List<TelephonyDisplayInfo>,
+ callback: DisplayInfoListener,
+ ) {
+ val len = infos.size
+ repeat(times) { index -> callback.onDisplayInfoChanged(infos[index % len]) }
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ return getTelephonyCallbackForType(telephonyManager)
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+
+ private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+ private const val SEP = "-"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index 621f793..d07b96f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.mockito.Mockito.verify
@@ -31,6 +35,19 @@
return callbackCaptor.allValues
}
+ /** Convenience constructor for SignalStrength */
+ fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
new file mode 100644
index 0000000..71bd511
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseShaderTest : SysuiTestCase() {
+
+ private lateinit var turbulenceNoiseShader: TurbulenceNoiseShader
+
+ @Test
+ fun compliesSimplexNoise() {
+ turbulenceNoiseShader = TurbulenceNoiseShader()
+ }
+
+ @Test
+ fun compliesFractalNoise() {
+ turbulenceNoiseShader = TurbulenceNoiseShader(useFractal = true)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index e185922..ee4e00b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -293,6 +293,8 @@
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private UserHandle mUser0;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -301,6 +303,8 @@
// For the purposes of this test, just run everything synchronously
ShellExecutor syncExecutor = new SyncExecutor();
+ mUser0 = createUserHande(/* userId= */ 0);
+
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
when(mNotificationShadeWindowView.getViewTreeObserver())
.thenReturn(mock(ViewTreeObserver.class));
@@ -339,7 +343,6 @@
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mock(PowerManager.class),
- mock(IDreamManager.class),
mock(AmbientDisplayConfiguration.class),
mock(StatusBarStateController.class),
mock(KeyguardStateController.class),
@@ -1242,6 +1245,24 @@
// Show the menu
stackView.showManageMenu(true);
assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ assertTrue(stackView.isManageMenuSettingsVisible());
+ }
+
+ @Test
+ public void testShowManageMenuChangesSysuiState_appBubble() {
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ assertFalse(stackView.isManageMenuSettingsVisible());
}
@Test
@@ -1650,7 +1671,7 @@
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true),
/* showInShade= */ eq(false));
@@ -1660,13 +1681,13 @@
@Test
public void testShowOrHideAppBubble_expandIfCollapsed() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.collapseStack();
assertThat(mBubbleController.isStackExpanded()).isFalse();
// Calling this while collapsed will expand the app bubble
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
@@ -1675,27 +1696,46 @@
@Test
public void testShowOrHideAppBubble_collapseIfSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
// Calling this while the app bubble is expanded should collapse the stack
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(mUser0);
+ }
+
+ @Test
+ public void testShowOrHideAppBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() {
+ UserHandle user10 = createUserHande(/* userId = */ 10);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
+
+ // Calling this while the app bubble is expanded should collapse the stack
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isFalse();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
}
@Test
public void testShowOrHideAppBubble_selectIfNotSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey());
assertThat(mBubbleController.isStackExpanded()).isTrue();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
@@ -1830,6 +1870,12 @@
mBubbleController.onUserChanged(userId);
}
+ private UserHandle createUserHande(int userId) {
+ UserHandle user = mock(UserHandle.class);
+ when(user.getIdentifier()).thenReturn(userId);
+ return user;
+ }
+
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index ceee0bc..4e14bbf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -20,7 +20,6 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.service.dreams.IDreamManager;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,7 +38,6 @@
TestableNotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
PowerManager powerManager,
- IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
@@ -53,7 +51,6 @@
UserTracker userTracker) {
super(contentResolver,
powerManager,
- dreamManager,
ambientDisplayConfiguration,
batteryController,
statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
index 9cdce20..1dda472 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -19,21 +19,16 @@
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
- private val _primaryBouncerVisible = MutableStateFlow(false)
- override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
- private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+ private val _primaryBouncerShow = MutableStateFlow(false)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
- private val _primaryBouncerHide = MutableStateFlow(false)
- override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
@@ -67,10 +62,6 @@
_primaryBouncerScrimmed.value = isScrimmed
}
- override fun setPrimaryVisible(isVisible: Boolean) {
- _primaryBouncerVisible.value = isVisible
- }
-
override fun setAlternateVisible(isVisible: Boolean) {
_isAlternateBouncerVisible.value = isVisible
}
@@ -79,18 +70,14 @@
_isAlternateBouncerUIAvailable.value = isAvailable
}
- override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
- _primaryBouncerShow.value = keyguardBouncerModel
+ override fun setPrimaryShow(isShowing: Boolean) {
+ _primaryBouncerShow.value = isShowing
}
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
- override fun setPrimaryHide(hide: Boolean) {
- _primaryBouncerHide.value = hide
- }
-
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
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 607439b..bd67889 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -23,6 +23,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
@@ -686,7 +687,7 @@
mListener = new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
- final InputDevice device = InputManager.getInstance().getInputDevice(
+ final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
deviceId);
Objects.requireNonNull(device, "Newly added input device was null.");
if (!device.getName().equals(deviceName)) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index e9a7f20..d94f4f2 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -194,19 +194,19 @@
private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
/** Used for both connected/disconnected, so match using key */
private Bundle mPowerOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
/** Used for both low/okay, so match using key */
private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
private MetricsLogger mMetricsLogger;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 19e5cb1..a3dc21e 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -320,7 +320,7 @@
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
.setDeliveryGroupMatchingFilter(matchingFilter)
.setDeliveryGroupExtrasMerger(extrasMerger)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c441859..409f054 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -35,7 +35,7 @@
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
per-file PinnerService.java = file:/apct-tests/perftests/OWNERS
-per-file RescueParty.java = fdunlap@google.com, shuc@google.com
+per-file RescueParty.java = fdunlap@google.com, shuc@google.com, ancr@google.com, harshitmahajan@google.com
per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
per-file TelephonyRegistry.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index d1e0f16..3de65f9 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -79,6 +79,7 @@
static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset";
static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot";
static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted";
+ static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
@VisibleForTesting
static final int LEVEL_NONE = 0;
@VisibleForTesting
@@ -105,10 +106,11 @@
@VisibleForTesting
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
+ @VisibleForTesting
+ static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
private static final String NAME = "rescue-party-observer";
-
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
@@ -327,8 +329,8 @@
}
}
- private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
- if (!mayPerformFactoryReset
+ private static int getMaxRescueLevel(boolean mayPerformReboot) {
+ if (!mayPerformReboot
|| SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
}
@@ -339,11 +341,11 @@
* Get the rescue level to perform if this is the n-th attempt at mitigating failure.
*
* @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
- * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
- * failure.
+ * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
+ * for the given failure.
* @return the rescue level for the n-th mitigation attempt.
*/
- private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
+ private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
if (mitigationCount == 1) {
return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
} else if (mitigationCount == 2) {
@@ -351,9 +353,9 @@
} else if (mitigationCount == 3) {
return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
} else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
+ return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
} else if (mitigationCount >= 5) {
- return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
+ return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
} else {
Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
return LEVEL_NONE;
@@ -450,6 +452,8 @@
break;
}
SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
+ long now = System.currentTimeMillis();
+ SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(now));
runnable = new Runnable() {
@Override
public void run() {
@@ -627,7 +631,7 @@
if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- mayPerformFactoryReset(failedPackage)));
+ mayPerformReboot(failedPackage)));
} else {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
@@ -642,7 +646,7 @@
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
final int level = getRescueLevel(mitigationCount,
- mayPerformFactoryReset(failedPackage));
+ mayPerformReboot(failedPackage));
executeRescueLevel(mContext,
failedPackage == null ? null : failedPackage.getPackageName(), level);
return true;
@@ -683,8 +687,9 @@
if (isDisabled()) {
return false;
}
+ boolean mayPerformReboot = !shouldThrottleReboot();
executeRescueLevel(mContext, /*failedPackage=*/ null,
- getRescueLevel(mitigationCount, true));
+ getRescueLevel(mitigationCount, mayPerformReboot));
return true;
}
@@ -698,14 +703,27 @@
* prompting a factory reset is an acceptable mitigation strategy for the package's
* failure, {@code false} otherwise.
*/
- private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+ private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
if (failingPackage == null) {
return false;
}
+ if (shouldThrottleReboot()) {
+ return false;
+ }
return isPersistentSystemApp(failingPackage.getPackageName());
}
+ /**
+ * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
+ * Will return {@code false} if a factory reset was already offered recently.
+ */
+ private boolean shouldThrottleReboot() {
+ Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
+ long now = System.currentTimeMillis();
+ return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+ }
+
private boolean isPersistentSystemApp(@NonNull String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5008fa..4a0a228 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -252,12 +252,6 @@
private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
- // How long we wait for a service to finish executing.
- static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
-
- // How long we wait for a service to finish executing.
- static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
-
// Foreground service types that always get immediate notification display,
// expressed in the same bitmask format that ServiceRecord.foregroundServiceType
// uses.
@@ -337,6 +331,13 @@
final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
/**
+ * A global counter for generating sequence numbers to uniquely identify bindService requests.
+ * It is purely for logging purposes.
+ */
+ @GuardedBy("mAm")
+ private long mBindServiceSeqCounter = 0;
+
+ /**
* Whether there is a rate limit that suppresses immediate re-deferral of new FGS
* notifications from each app. On by default, disabled only by shell command for
* test-suite purposes. To disable the behavior more generally, use the usual
@@ -4429,8 +4430,12 @@
try {
bumpServiceExecutingLocked(r, execInFg, "bind",
OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+ + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
+ }
r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
- r.app.mState.getReportedProcState());
+ r.app.mState.getReportedProcState(), mBindServiceSeqCounter++);
if (!rebind) {
i.requested = true;
}
@@ -6598,13 +6603,15 @@
return;
}
final ProcessServiceRecord psr = proc.mServices;
- if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null) {
+ if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
+ || proc.isKilled()) {
return;
}
final long now = SystemClock.uptimeMillis();
final long maxTime = now
- (psr.shouldExecServicesFg()
- ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+ ? mAm.mConstants.SERVICE_TIMEOUT
+ : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
long nextTime = 0;
for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6635,8 +6642,8 @@
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
- ? (nextTime + SERVICE_TIMEOUT) :
- (nextTime + SERVICE_BACKGROUND_TIMEOUT));
+ ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+ (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
}
}
@@ -6732,7 +6739,7 @@
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
- ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+ ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5696004..4fa28a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -248,7 +248,16 @@
private static final long DEFAULT_SERVICE_BIND_ALMOST_PERCEPTIBLE_TIMEOUT_MS = 15 * 1000;
- // Flag stored in the DeviceConfig API.
+ /**
+ * Default value to {@link #SERVICE_TIMEOUT}.
+ */
+ private static final long DEFAULT_SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /**
+ * Default value to {@link #SERVICE_BACKGROUND_TIMEOUT}.
+ */
+ private static final long DEFAULT_SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT * 10;
+
/**
* Maximum number of cached processes.
*/
@@ -506,6 +515,12 @@
// to restart less than this amount of time from the last one.
public long SERVICE_MIN_RESTART_TIME_BETWEEN = DEFAULT_SERVICE_MIN_RESTART_TIME_BETWEEN;
+ // How long we wait for a service to finish executing.
+ long SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+
+ // How long we wait for a service to finish executing.
+ long SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_BACKGROUND_TIMEOUT;
+
// Maximum amount of time for there to be no activity on a service before
// we consider it non-essential and allow its process to go on the
// LRU background list.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c74aa7f..7550196 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14507,18 +14507,6 @@
}
}
- // resultTo broadcasts are always infinitely deferrable.
- if ((resultTo != null) && !ordered && mEnableModernQueue) {
- if (brOptions == null) {
- brOptions = BroadcastOptions.makeBasic();
- }
- brOptions.setDeferUntilActive(true);
- }
-
- if (mEnableModernQueue && ordered && brOptions != null && brOptions.isDeferUntilActive()) {
- throw new IllegalArgumentException("Ordered broadcasts can't be deferred until active");
- }
-
// Verify that protected broadcasts are only being sent by system code,
// and that system code is only sending protected broadcasts.
final boolean isProtectedBroadcast;
@@ -19729,8 +19717,9 @@
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(code, new AttributionSource(shellUid,
- "com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
+ Process.INVALID_PID, "com.android.shell",
+ attributionSource.getAttributionTag(), attributionSource.getToken(),
+ /*renouncedPermissions*/ null, attributionSource.getNext()),
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skiProxyOperation);
} finally {
@@ -19781,8 +19770,9 @@
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(clientId, code, new AttributionSource(shellUid,
- "com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
+ Process.INVALID_PID, "com.android.shell",
+ attributionSource.getAttributionTag(), attributionSource.getToken(),
+ /*renouncedPermissions*/ null, attributionSource.getNext()),
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -19806,8 +19796,9 @@
final long identity = Binder.clearCallingIdentity();
try {
superImpl.apply(clientId, code, new AttributionSource(shellUid,
- "com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
+ Process.INVALID_PID, "com.android.shell",
+ attributionSource.getAttributionTag(), attributionSource.getToken(),
+ /*renouncedPermissions*/ null, attributionSource.getNext()),
skipProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f236a96..d09ca5c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -30,7 +30,6 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.RequiresNoPermission;
-import android.app.AlarmManager;
import android.app.StatsManager;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -414,18 +413,6 @@
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
}
- final AlarmManager am = mContext.getSystemService(AlarmManager.class);
- mHandler.post(() -> {
- synchronized (mStats) {
- mStats.setLongPlugInAlarmInterface(new AlarmInterface(am, () -> {
- synchronized (mStats) {
- if (mStats.isOnBattery()) return;
- mStats.maybeResetWhilePluggedInLocked();
- }
- }));
- }
- });
-
synchronized (mPowerStatsLock) {
mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
if (mPowerStatsInternal != null) {
@@ -2529,32 +2516,6 @@
}
}
- final class AlarmInterface implements BatteryStatsImpl.AlarmInterface,
- AlarmManager.OnAlarmListener {
- private AlarmManager mAm;
- private Runnable mOnAlarm;
-
- AlarmInterface(AlarmManager am, Runnable onAlarm) {
- mAm = am;
- mOnAlarm = onAlarm;
- }
-
- @Override
- public void schedule(long rtcTimeMs, long windowLengthMs) {
- mAm.setWindow(AlarmManager.RTC, rtcTimeMs, windowLengthMs, TAG, this, mHandler);
- }
-
- @Override
- public void cancel() {
- mAm.cancel(this);
- }
-
- @Override
- public void onAlarm() {
- mOnAlarm.run();
- }
- }
-
private static native int nativeWaitWakeup(ByteBuffer outBuffer);
private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 53fcddf..33d4004 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -236,6 +236,14 @@
private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
ActivityManager.isLowRamDeviceStatic() ? 256 : 1024;
+ /**
+ * For {@link BroadcastRecord}: Default to treating all broadcasts sent by
+ * the system as be {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+ */
+ public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE;
+ private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
+ private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false;
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -373,7 +381,12 @@
DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+ CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE,
+ DEFAULT_CORE_DEFER_UNTIL_ACTIVE);
}
+
+ // TODO: migrate BroadcastRecord to accept a BroadcastConstants
+ BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = CORE_DEFER_UNTIL_ACTIVE;
}
/**
@@ -418,6 +431,8 @@
MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
+ pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE,
+ CORE_DEFER_UNTIL_ACTIVE).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 59f33dd..6bd3c79 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -90,6 +90,7 @@
final boolean prioritized; // contains more than one priority tranche
final boolean deferUntilActive; // infinitely deferrable broadcast
final boolean shareIdentity; // whether the broadcaster's identity should be shared
+ final boolean urgent; // has been classified as "urgent"
final int userId; // user id this broadcast was for
final @Nullable String resolvedType; // the resolved data type
final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -146,6 +147,13 @@
private @Nullable String mCachedToString;
private @Nullable String mCachedToShortString;
+ /**
+ * When enabled, assume that {@link UserHandle#isCore(int)} apps should
+ * treat {@link BroadcastOptions#DEFERRAL_POLICY_DEFAULT} as
+ * {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+ */
+ static boolean CORE_DEFER_UNTIL_ACTIVE = false;
+
/** Empty immutable list of receivers */
static final List<Object> EMPTY_RECEIVERS = List.of();
@@ -400,7 +408,9 @@
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
deliveryReasons = new String[delivery.length];
- deferUntilActive = options != null ? options.isDeferUntilActive() : false;
+ urgent = calculateUrgent(_intent, _options);
+ deferUntilActive = calculateDeferUntilActive(_callingUid,
+ _options, _resultTo, _serialized, urgent);
deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
@@ -488,6 +498,7 @@
pushMessageOverQuota = from.pushMessageOverQuota;
interactive = from.interactive;
shareIdentity = from.shareIdentity;
+ urgent = from.urgent;
filterExtrasForReceiver = from.filterExtrasForReceiver;
}
@@ -681,15 +692,8 @@
return deferUntilActive;
}
- /**
- * Core policy determination about this broadcast's delivery prioritization
- */
boolean isUrgent() {
- // TODO: flags for controlling policy
- // TODO: migrate alarm-prioritization flag to BroadcastConstants
- return (isForeground()
- || interactive
- || alarm);
+ return urgent;
}
@NonNull String getHostingRecordTriggerType() {
@@ -849,6 +853,69 @@
}
}
+ /**
+ * Core policy determination about this broadcast's delivery prioritization
+ */
+ @VisibleForTesting
+ static boolean calculateUrgent(@NonNull Intent intent, @Nullable BroadcastOptions options) {
+ // TODO: flags for controlling policy
+ // TODO: migrate alarm-prioritization flag to BroadcastConstants
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
+ return true;
+ }
+ if (options != null) {
+ if (options.isInteractive()) {
+ return true;
+ }
+ if (options.isAlarmBroadcast()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Resolve the requested {@link BroadcastOptions#setDeferralPolicy(int)}
+ * against this broadcast state to determine if it should be marked as
+ * "defer until active".
+ */
+ @VisibleForTesting
+ static boolean calculateDeferUntilActive(int callingUid, @Nullable BroadcastOptions options,
+ @Nullable IIntentReceiver resultTo, boolean ordered, boolean urgent) {
+ // Ordered broadcasts can never be deferred until active
+ if (ordered) {
+ return false;
+ }
+
+ // Unordered resultTo broadcasts are always deferred until active
+ if (!ordered && resultTo != null) {
+ return true;
+ }
+
+ // Determine if a strong preference in either direction was expressed;
+ // a preference here overrides all remaining policies
+ if (options != null) {
+ switch (options.getDeferralPolicy()) {
+ case BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE:
+ return true;
+ case BroadcastOptions.DEFERRAL_POLICY_NONE:
+ return false;
+ }
+ }
+
+ // Urgent broadcasts aren't deferred until active
+ if (urgent) {
+ return false;
+ }
+
+ // Otherwise, choose a reasonable default
+ if (CORE_DEFER_UNTIL_ACTIVE && UserHandle.isCore(callingUid)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index ddc9e91..844f175 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.text.TextFlags;
import android.widget.WidgetFlags;
import com.android.internal.R;
@@ -162,6 +163,11 @@
DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ASPECT_RATIO,
WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class,
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
+
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+ TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
// add other device configs here...
}
private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index bac9253..8e93c1b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -69,7 +69,7 @@
// define the encoding of that data in an integer.
static final int MAX_HISTORIC_STATES = 8; // Maximum number of historic states we will keep.
- static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
+ static final String STATE_FILE_PREFIX = "state-v2-"; // Prefix to use for state filenames.
static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
static long WRITE_PERIOD = 30*60*1000; // Write file every 30 minutes or so.
@@ -462,6 +462,10 @@
File file = files[i];
String fileStr = file.getPath();
if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr);
+ if (!file.getName().startsWith(STATE_FILE_PREFIX)) {
+ if (DEBUG) Slog.d(TAG, "Skipping: mismatching prefix");
+ continue;
+ }
if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) {
if (DEBUG) Slog.d(TAG, "Skipping: already checked in");
continue;
@@ -478,6 +482,14 @@
@GuardedBy("mFileLock")
private void trimHistoricStatesWriteLF() {
+ File[] files = mBaseDir.listFiles();
+ if (files != null) {
+ for (int i = 0; i < files.length; i++) {
+ if (!files[i].getName().startsWith(STATE_FILE_PREFIX)) {
+ files[i].delete();
+ }
+ }
+ }
ArrayList<String> filesArray = getCommittedFilesLF(MAX_HISTORIC_STATES, false, true);
if (filesArray == null) {
return;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 490a33e..3e86c45 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1372,8 +1372,8 @@
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (mMonitorRotation) {
RotationHelper.init(mContext, mAudioHandler,
- rotationParam -> onRotationUpdate(rotationParam),
- foldParam -> onFoldUpdate(foldParam));
+ rotation -> onRotationUpdate(rotation),
+ foldState -> onFoldStateUpdate(foldState));
}
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1515,16 +1515,20 @@
//-----------------------------------------------------------------
// rotation/fold updates coming from RotationHelper
- void onRotationUpdate(String rotationParameter) {
+ void onRotationUpdate(Integer rotation) {
+ mSpatializerHelper.setDisplayOrientation((float) (rotation * Math.PI / 180.));
// use REPLACE as only the last rotation matters
+ final String rotationParameter = "rotation=" + rotation;
sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
/*obj*/ rotationParameter, /*delay*/ 0);
}
- void onFoldUpdate(String foldParameter) {
+ void onFoldStateUpdate(Boolean foldState) {
+ mSpatializerHelper.setFoldState(foldState);
// use REPLACE as only the last fold state matters
+ final String foldStateParameter = "device_folded=" + (foldState ? "on" : "off");
sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
- /*obj*/ foldParameter, /*delay*/ 0);
+ /*obj*/ foldStateParameter, /*delay*/ 0);
}
//-----------------------------------------------------------------
@@ -1740,6 +1744,9 @@
mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
mSoundDoseHelper.reset();
+ // Restore rotation information.
+ RotationHelper.forceUpdate();
+
onIndicateSystemReady();
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
@@ -8170,7 +8177,7 @@
volumeChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
volumeChangedOptions.setDeliveryGroupMatchingKey(
AudioManager.VOLUME_CHANGED_ACTION, String.valueOf(mStreamType));
- volumeChangedOptions.setDeferUntilActive(true);
+ volumeChangedOptions.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
mVolumeChangedOptions = volumeChangedOptions.toBundle();
mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
@@ -8179,7 +8186,8 @@
streamDevicesChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
streamDevicesChangedOptions.setDeliveryGroupMatchingKey(
AudioManager.STREAM_DEVICES_CHANGED_ACTION, String.valueOf(mStreamType));
- streamDevicesChangedOptions.setDeferUntilActive(true);
+ streamDevicesChangedOptions.setDeferralPolicy(
+ BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle();
}
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index 5cdf58b..394e4af 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -55,14 +55,14 @@
private static AudioDisplayListener sDisplayListener;
private static FoldStateListener sFoldStateListener;
/** callback to send rotation updates to AudioSystem */
- private static Consumer<String> sRotationUpdateCb;
+ private static Consumer<Integer> sRotationCallback;
/** callback to send folded state updates to AudioSystem */
- private static Consumer<String> sFoldUpdateCb;
+ private static Consumer<Boolean> sFoldStateCallback;
private static final Object sRotationLock = new Object();
private static final Object sFoldStateLock = new Object();
- private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
- private static boolean sDeviceFold = true; // R/W synchronized on sFoldStateLock
+ private static Integer sRotation = null; // R/W synchronized on sRotationLock
+ private static Boolean sFoldState = null; // R/W synchronized on sFoldStateLock
private static Context sContext;
private static Handler sHandler;
@@ -73,15 +73,15 @@
* - sContext != null
*/
static void init(Context context, Handler handler,
- Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
+ Consumer<Integer> rotationCallback, Consumer<Boolean> foldStateCallback) {
if (context == null) {
throw new IllegalArgumentException("Invalid null context");
}
sContext = context;
sHandler = handler;
sDisplayListener = new AudioDisplayListener();
- sRotationUpdateCb = rotationUpdateCb;
- sFoldUpdateCb = foldUpdateCb;
+ sRotationCallback = rotationCallback;
+ sFoldStateCallback = foldStateCallback;
enable();
}
@@ -112,9 +112,9 @@
int newRotation = DisplayManagerGlobal.getInstance()
.getDisplayInfo(Display.DEFAULT_DISPLAY).rotation;
synchronized(sRotationLock) {
- if (newRotation != sDeviceRotation) {
- sDeviceRotation = newRotation;
- publishRotation(sDeviceRotation);
+ if (sRotation == null || sRotation != newRotation) {
+ sRotation = newRotation;
+ publishRotation(sRotation);
}
}
}
@@ -123,43 +123,52 @@
if (DEBUG_ROTATION) {
Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
}
- String rotationParam;
+ int rotationDegrees;
switch (rotation) {
case Surface.ROTATION_0:
- rotationParam = "rotation=0";
+ rotationDegrees = 0;
break;
case Surface.ROTATION_90:
- rotationParam = "rotation=90";
+ rotationDegrees = 90;
break;
case Surface.ROTATION_180:
- rotationParam = "rotation=180";
+ rotationDegrees = 180;
break;
case Surface.ROTATION_270:
- rotationParam = "rotation=270";
+ rotationDegrees = 270;
break;
default:
Log.e(TAG, "Unknown device rotation");
- rotationParam = null;
+ rotationDegrees = -1;
}
- if (rotationParam != null) {
- sRotationUpdateCb.accept(rotationParam);
+ if (rotationDegrees != -1) {
+ sRotationCallback.accept(rotationDegrees);
}
}
/**
* publish the change of device folded state if any.
*/
- static void updateFoldState(boolean newFolded) {
+ static void updateFoldState(boolean foldState) {
synchronized (sFoldStateLock) {
- if (sDeviceFold != newFolded) {
- sDeviceFold = newFolded;
- String foldParam;
- if (newFolded) {
- foldParam = "device_folded=on";
- } else {
- foldParam = "device_folded=off";
- }
- sFoldUpdateCb.accept(foldParam);
+ if (sFoldState == null || sFoldState != foldState) {
+ sFoldState = foldState;
+ sFoldStateCallback.accept(foldState);
+ }
+ }
+ }
+
+ /**
+ * forceUpdate is called when audioserver restarts.
+ */
+ static void forceUpdate() {
+ synchronized (sRotationLock) {
+ sRotation = null;
+ }
+ updateOrientation(); // We will get at least one orientation update now.
+ synchronized (sFoldStateLock) {
+ if (sFoldState != null) {
+ sFoldStateCallback.accept(sFoldState);
}
}
}
@@ -185,4 +194,4 @@
updateOrientation();
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 6a97243..c248367 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -41,6 +41,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
+import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -52,7 +53,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
@@ -112,6 +112,8 @@
private static final long GLOBAL_TIME_OFFSET_UNINITIALIZED = -1;
+ private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
+
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
@@ -132,15 +134,6 @@
// For now using the same value for CSD supported devices
private float mSafeMediaVolumeDbfs;
- private static class SafeDeviceVolumeInfo {
- int mDeviceType;
- int mSafeVolumeIndex = -1;
-
- SafeDeviceVolumeInfo(int deviceType) {
- mDeviceType = deviceType;
- }
- }
-
/**
* mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
* Contains a safe volume index for a given device type.
@@ -152,25 +145,7 @@
* This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
* the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
*/
- private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices =
- new HashMap<>() {{
- put(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET));
- put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE));
- put(AudioSystem.DEVICE_OUT_USB_HEADSET,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET));
- put(AudioSystem.DEVICE_OUT_BLE_HEADSET,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET));
- put(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST));
- put(AudioSystem.DEVICE_OUT_HEARING_AID,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID));
- put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES));
- put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
- }};
+ private final SparseIntArray mSafeMediaVolumeDevices = new SparseIntArray();
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -291,6 +266,7 @@
mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
initCsd();
+ initSafeVolumes();
mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
@@ -305,6 +281,25 @@
Context.ALARM_SERVICE);
}
+ void initSafeVolumes() {
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_USB_HEADSET,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_HEADSET,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_HEARING_AID,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ SAFE_MEDIA_VOLUME_UNINITIALIZED);
+ }
+
float getRs2Value() {
if (!mEnableCsd) {
return 0.f;
@@ -435,12 +430,12 @@
}
/*package*/ int safeMediaVolumeIndex(int device) {
- final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device);
- if (vi == null) {
+ final int vol = mSafeMediaVolumeDevices.get(device);
+ if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
}
- return vi.mSafeVolumeIndex;
+ return vol;
}
/*package*/ void restoreMusicActiveMs() {
@@ -465,14 +460,15 @@
AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
AudioSystem.STREAM_MUSIC);
- for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
- int index = streamState.getIndex(vi.mDeviceType);
- int safeIndex = safeMediaVolumeIndex(vi.mDeviceType);
+ for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i) {
+ int deviceType = mSafeMediaVolumeDevices.keyAt(i);
+ int index = streamState.getIndex(deviceType);
+ int safeIndex = safeMediaVolumeIndex(deviceType);
if (index > safeIndex) {
- streamState.setIndex(safeIndex, vi.mDeviceType, caller,
+ streamState.setIndex(safeIndex, deviceType, caller,
true /*hasModifyAudioSettings*/);
mAudioHandler.sendMessageAtTime(
- mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType,
+ mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, deviceType,
/*arg2=*/0, streamState), /*delay=*/0);
}
}
@@ -494,7 +490,7 @@
private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
&& (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
- && (mSafeMediaVolumeDevices.containsKey(device))
+ && safeDevicesContains(device)
&& (index > safeMediaVolumeIndex(device));
}
@@ -546,7 +542,7 @@
synchronized (mSafeMediaVolumeStateLock) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
- if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) {
+ if (safeDevicesContains(device) && isStreamActive) {
scheduleMusicActiveCheck();
int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
device);
@@ -574,10 +570,8 @@
/*package*/ void configureSafeMedia(boolean forced, String caller) {
int msg = MSG_CONFIGURE_SAFE_MEDIA;
- if (forced) {
- // unforced should not cancel forced configure messages
- mAudioHandler.removeMessages(msg);
- }
+
+ mAudioHandler.removeMessages(msg);
long time = 0;
if (forced) {
@@ -591,14 +585,15 @@
}
/*package*/ void initSafeMediaVolumeIndex() {
- for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
- vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType);
+ for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i) {
+ int deviceType = mSafeMediaVolumeDevices.keyAt(i);
+ mSafeMediaVolumeDevices.put(deviceType, getSafeDeviceMediaVolumeIndex(deviceType));
}
}
/*package*/ int getSafeMediaVolumeIndex(int device) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
- && mSafeMediaVolumeDevices.containsKey(device)) {
+ && safeDevicesContains(device)) {
return safeMediaVolumeIndex(device);
} else {
return -1;
@@ -616,7 +611,7 @@
}
/*package*/ boolean safeDevicesContains(int device) {
- return mSafeMediaVolumeDevices.containsKey(device);
+ return mSafeMediaVolumeDevices.indexOfKey(device) >= 0;
}
/*package*/ void invalidatPendingVolumeCommand() {
@@ -667,9 +662,9 @@
pw.print(" mSafeMediaVolumeState=");
pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
- for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
- pw.print(" mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType);
- pw.print("]="); pw.println(vi.mSafeVolumeIndex);
+ for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i) {
+ pw.print(" mSafeMediaVolumeIndex["); pw.print(mSafeMediaVolumeDevices.keyAt(i));
+ pw.print("]="); pw.println(mSafeMediaVolumeDevices.valueAt(i));
}
pw.print(" mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
@@ -723,7 +718,7 @@
}
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
- && mSafeMediaVolumeDevices.containsKey(device)) {
+ && safeDevicesContains(device)) {
soundDose.updateAttenuation(
AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
(newIndex + 5) / 10,
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 3ea4f4f..8f54e45 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1066,7 +1066,7 @@
if (transform.length != 6) {
throw new IllegalArgumentException("invalid array size" + transform.length);
}
- if (!checkSpatForHeadTracking("setGlobalTransform")) {
+ if (!checkSpatializerForHeadTracking("setGlobalTransform")) {
return;
}
try {
@@ -1077,7 +1077,7 @@
}
synchronized void recenterHeadTracker() {
- if (!checkSpatForHeadTracking("recenterHeadTracker")) {
+ if (!checkSpatializerForHeadTracking("recenterHeadTracker")) {
return;
}
try {
@@ -1087,8 +1087,30 @@
}
}
+ synchronized void setDisplayOrientation(float displayOrientation) {
+ if (!checkSpatializer("setDisplayOrientation")) {
+ return;
+ }
+ try {
+ mSpat.setDisplayOrientation(displayOrientation);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setDisplayOrientation", e);
+ }
+ }
+
+ synchronized void setFoldState(boolean folded) {
+ if (!checkSpatializer("setFoldState")) {
+ return;
+ }
+ try {
+ mSpat.setFoldState(folded);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setFoldState", e);
+ }
+ }
+
synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
- if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) {
+ if (!checkSpatializerForHeadTracking("setDesiredHeadTrackingMode")) {
return;
}
if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
@@ -1186,7 +1208,7 @@
return mHeadTrackerAvailable;
}
- private boolean checkSpatForHeadTracking(String funcName) {
+ private boolean checkSpatializer(String funcName) {
switch (mState) {
case STATE_UNINITIALIZED:
case STATE_NOT_SUPPORTED:
@@ -1197,14 +1219,18 @@
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
// try to recover by resetting the native spatializer state
- Log.e(TAG, "checkSpatForHeadTracking(): "
- + "native spatializer should not be null in state: " + mState);
+ Log.e(TAG, "checkSpatializer(): called from " + funcName
+ + "(), native spatializer should not be null in state: " + mState);
postReset();
return false;
}
break;
}
- return mIsHeadTrackingSupported;
+ return true;
+ }
+
+ private boolean checkSpatializerForHeadTracking(String funcName) {
+ return checkSpatializer(funcName) && mIsHeadTrackingSupported;
}
private void dispatchActualHeadTrackingMode(int newMode) {
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 8b80674..daca00b 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -6,5 +6,24 @@
{
"name": "CtsBiometricsHostTestCases"
}
- ]
-}
\ No newline at end of file
+ ],
+ "ironwood-postsubmit": [
+ {
+ "name": "BiometricsE2eTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.platform.test.scenario.biometrics"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index dc00ffc..01ffc7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -314,7 +314,8 @@
final FingerprintSensorPropertiesInternal sensorProps =
provider.second.getSensorProperties(options.getSensorId());
if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
- && sensorProps != null && sensorProps.isAnyUdfpsType()) {
+ && sensorProps != null && (sensorProps.isAnyUdfpsType()
+ || sensorProps.isAnySidefpsType())) {
try {
return authenticateWithPrompt(operationId, sensorProps, callingUid,
callingUserId, receiver, opPackageName,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 4e3de3c..e8af840 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -243,11 +243,13 @@
public List<CameraStreamStats> mStreamStats;
public String mUserTag;
public int mVideoStabilizationMode;
+ public final long mLogId;
private long mDurationOrStartTimeMs; // Either start time, or duration once completed
CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel,
- boolean isNdk, int action, int latencyMs, int operatingMode, boolean deviceError) {
+ boolean isNdk, int action, int latencyMs, int operatingMode, boolean deviceError,
+ long logId) {
mCameraId = cameraId;
mCameraFacing = facing;
mClientName = clientName;
@@ -259,6 +261,7 @@
mLatencyMs = latencyMs;
mOperatingMode = operatingMode;
mDeviceError = deviceError;
+ mLogId = logId;
}
public void markCompleted(int internalReconfigure, long requestCount,
@@ -840,7 +843,8 @@
+ ", deviceError " + e.mDeviceError
+ ", streamCount is " + streamCount
+ ", userTag is " + e.mUserTag
- + ", videoStabilizationMode " + e.mVideoStabilizationMode);
+ + ", videoStabilizationMode " + e.mVideoStabilizationMode
+ + ", logId " + e.mLogId);
}
// Convert from CameraStreamStats to CameraStreamProto
CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
@@ -900,7 +904,7 @@
MessageNano.toByteArray(streamProtos[2]),
MessageNano.toByteArray(streamProtos[3]),
MessageNano.toByteArray(streamProtos[4]),
- e.mUserTag, e.mVideoStabilizationMode);
+ e.mUserTag, e.mVideoStabilizationMode, e.mLogId);
}
}
@@ -1089,6 +1093,7 @@
List<CameraStreamStats> streamStats = cameraState.getStreamStats();
String userTag = cameraState.getUserTag();
int videoStabilizationMode = cameraState.getVideoStabilizationMode();
+ long logId = cameraState.getLogId();
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1110,7 +1115,7 @@
CameraUsageEvent openEvent = new CameraUsageEvent(
cameraId, facing, clientName, apiLevel, isNdk,
FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__OPEN,
- latencyMs, sessionType, deviceError);
+ latencyMs, sessionType, deviceError, logId);
mCameraUsageHistory.add(openEvent);
break;
case CameraSessionStats.CAMERA_STATE_ACTIVE:
@@ -1137,7 +1142,7 @@
CameraUsageEvent newEvent = new CameraUsageEvent(
cameraId, facing, clientName, apiLevel, isNdk,
FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__SESSION,
- latencyMs, sessionType, deviceError);
+ latencyMs, sessionType, deviceError, logId);
CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
if (oldEvent != null) {
Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
@@ -1181,7 +1186,7 @@
CameraUsageEvent closeEvent = new CameraUsageEvent(
cameraId, facing, clientName, apiLevel, isNdk,
FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__CLOSE,
- latencyMs, sessionType, deviceError);
+ latencyMs, sessionType, deviceError, logId);
mCameraUsageHistory.add(closeEvent);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index a589313..00af224 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -76,7 +76,13 @@
* This flag indicates that the corresponding state should be disabled when the device is
* overheating and reaching the critical status.
*/
- public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+ public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+
+ /**
+ * This flag indicates that the corresponding state should be disabled when power save mode
+ * is enabled.
+ */
+ public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5;
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
@@ -84,7 +90,8 @@
FLAG_APP_INACCESSIBLE,
FLAG_EMULATED_ONLY,
FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
- FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL
+ FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+ FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 43ee5e2..9645690 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED;
import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
@@ -609,7 +610,8 @@
@GuardedBy("mLock")
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
- @OverrideRequestController.RequestStatus int status, int flags) {
+ @OverrideRequestController.RequestStatus int status,
+ @OverrideRequestController.StatusChangedFlag int flags) {
if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
switch (status) {
case STATUS_ACTIVE:
@@ -641,6 +643,10 @@
mDeviceStateNotificationController
.showThermalCriticalNotificationIfNeeded(
request.getRequestedState());
+ } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) {
+ mDeviceStateNotificationController
+ .showPowerSaveNotificationIfNeeded(
+ request.getRequestedState());
}
}
break;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
index 9008740..ab261ac 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -16,6 +16,8 @@
package com.android.server.devicestate;
+import static android.provider.Settings.ACTION_BATTERY_SAVER_SETTINGS;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -101,10 +103,16 @@
}
String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
if (requesterApplicationLabel != null) {
+ final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+ .setPackage(mContext.getPackageName());
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
showNotification(
info.name, info.activeNotificationTitle,
String.format(info.activeNotificationContent, requesterApplicationLabel),
- true /* ongoing */, R.drawable.ic_dual_screen
+ true /* ongoing */, R.drawable.ic_dual_screen,
+ pendingIntent,
+ mContext.getString(R.string.device_state_notification_turn_off_button)
);
} else {
Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
@@ -126,7 +134,33 @@
showNotification(
info.name, info.thermalCriticalNotificationTitle,
info.thermalCriticalNotificationContent, false /* ongoing */,
- R.drawable.ic_thermostat
+ R.drawable.ic_thermostat,
+ null /* pendingIntent */,
+ null /* actionText */
+ );
+ }
+
+ /**
+ * Displays the notification indicating that the device state is canceled due to power
+ * save mode being enabled. Does nothing if the state does not have a power save mode
+ * notification.
+ *
+ * @param state the identifier of the device state being canceled.
+ */
+ void showPowerSaveNotificationIfNeeded(int state) {
+ NotificationInfo info = mNotificationInfos.get(state);
+ if (info == null || !info.hasPowerSaveModeNotification()) {
+ return;
+ }
+ final Intent intent = new Intent(ACTION_BATTERY_SAVER_SETTINGS);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+ showNotification(
+ info.name, info.powerSaveModeNotificationTitle,
+ info.powerSaveModeNotificationContent, false /* ongoing */,
+ R.drawable.ic_thermostat,
+ pendingIntent,
+ mContext.getString(R.string.device_state_notification_settings_button)
);
}
@@ -161,7 +195,8 @@
*/
private void showNotification(
@NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing,
- @DrawableRes int iconRes) {
+ @DrawableRes int iconRes,
+ @Nullable PendingIntent pendingIntent, @Nullable String actionText) {
final NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
@@ -173,14 +208,10 @@
.setOngoing(ongoing)
.setCategory(Notification.CATEGORY_SYSTEM);
- if (ongoing) {
- final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
- .setPackage(mContext.getPackageName());
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+ if (pendingIntent != null && actionText != null) {
final Notification.Action action = new Notification.Action.Builder(
null /* icon */,
- mContext.getString(R.string.device_state_notification_turn_off_button),
+ actionText,
pendingIntent)
.build();
builder.addAction(action);
@@ -215,12 +246,21 @@
final String[] thermalCriticalNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_thermal_contents);
+ final String[] powerSaveModeNotificationTitles =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_power_save_titles);
+ final String[] powerSaveModeNotificationContents =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_power_save_contents);
+
if (stateIdentifiers.length != names.length
|| stateIdentifiers.length != activeNotificationTitles.length
|| stateIdentifiers.length != activeNotificationContents.length
|| stateIdentifiers.length != thermalCriticalNotificationTitles.length
|| stateIdentifiers.length != thermalCriticalNotificationContents.length
+ || stateIdentifiers.length != powerSaveModeNotificationTitles.length
+ || stateIdentifiers.length != powerSaveModeNotificationContents.length
) {
throw new IllegalStateException(
"The length of state identifiers and notification texts must match!");
@@ -237,7 +277,9 @@
new NotificationInfo(
names[i], activeNotificationTitles[i], activeNotificationContents[i],
thermalCriticalNotificationTitles[i],
- thermalCriticalNotificationContents[i])
+ thermalCriticalNotificationContents[i],
+ powerSaveModeNotificationTitles[i],
+ powerSaveModeNotificationContents[i])
);
}
@@ -272,16 +314,21 @@
public final String activeNotificationContent;
public final String thermalCriticalNotificationTitle;
public final String thermalCriticalNotificationContent;
+ public final String powerSaveModeNotificationTitle;
+ public final String powerSaveModeNotificationContent;
NotificationInfo(String name, String activeNotificationTitle,
String activeNotificationContent, String thermalCriticalNotificationTitle,
- String thermalCriticalNotificationContent) {
+ String thermalCriticalNotificationContent, String powerSaveModeNotificationTitle,
+ String powerSaveModeNotificationContent) {
this.name = name;
this.activeNotificationTitle = activeNotificationTitle;
this.activeNotificationContent = activeNotificationContent;
this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+ this.powerSaveModeNotificationTitle = powerSaveModeNotificationTitle;
+ this.powerSaveModeNotificationContent = powerSaveModeNotificationContent;
}
boolean hasActiveNotification() {
@@ -292,5 +339,10 @@
return thermalCriticalNotificationTitle != null
&& thermalCriticalNotificationTitle.length() > 0;
}
+
+ boolean hasPowerSaveModeNotification() {
+ return powerSaveModeNotificationTitle != null
+ && powerSaveModeNotificationTitle.length() > 0;
+ }
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index fecc13f..af33de0 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -52,11 +52,24 @@
*/
int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
+ /**
+ * Indicating that the supported device states have changed because power save mode was enabled.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED = 4;
+
+ /**
+ * Indicating that the supported device states have changed because power save mode was
+ * disabled.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5;
+
@IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
- SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+ SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL,
+ SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED,
+ SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
@interface SupportedStatesUpdatedReason {}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 2ed4765..46f0bc0 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -64,6 +64,18 @@
*/
static final int FLAG_THERMAL_CRITICAL = 1 << 0;
+ /**
+ * A flag indicating that the status change was triggered by power save mode.
+ */
+ static final int FLAG_POWER_SAVE_ENABLED = 1 << 1;
+
+ @IntDef(flag = true, prefix = {"FLAG_"}, value = {
+ FLAG_THERMAL_CRITICAL,
+ FLAG_POWER_SAVE_ENABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface StatusChangedFlag {}
+
static String statusToString(@RequestStatus int status) {
switch (status) {
case STATUS_ACTIVE:
@@ -228,13 +240,18 @@
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
boolean isThermalCritical =
reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+ boolean isPowerSaveEnabled =
+ reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
+ @StatusChangedFlag int flags = 0;
+ flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0;
+ flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0;
if (mBaseStateRequest != null && !contains(newSupportedStates,
mBaseStateRequest.getRequestedState())) {
- cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
+ cancelCurrentBaseStateRequestLocked(flags);
}
if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
- cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
+ cancelCurrentRequestLocked(flags);
}
}
@@ -255,7 +272,8 @@
cancelRequestLocked(requestToCancel, 0 /* flags */);
}
- private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+ private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel,
+ @StatusChangedFlag int flags) {
mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
}
@@ -267,7 +285,7 @@
cancelCurrentRequestLocked(0 /* flags */);
}
- private void cancelCurrentRequestLocked(int flags) {
+ private void cancelCurrentRequestLocked(@StatusChangedFlag int flags) {
if (mRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
@@ -285,7 +303,7 @@
cancelCurrentBaseStateRequestLocked(0 /* flags */);
}
- private void cancelCurrentBaseStateRequestLocked(int flags) {
+ private void cancelCurrentBaseStateRequestLocked(@StatusChangedFlag int flags) {
if (mBaseStateRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
@@ -312,6 +330,6 @@
* cancelled request.
*/
void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
- int flags);
+ @StatusChangedFlag int flags);
}
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index db6944d0..3864200 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2088,7 +2088,7 @@
}
@VisibleForTesting
- void onRefreshRateSettingChangedLocked(float min, float max) {
+ public void onRefreshRateSettingChangedLocked(float min, float max) {
boolean changeable = (max - min > 1f && max > 60f);
if (mRefreshRateChangeable != changeable) {
mRefreshRateChangeable = changeable;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 3e2efdd..20ff51c 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -120,7 +120,7 @@
options.setDeliveryGroupMatchingKey(
DREAMING_DELIVERY_GROUP_NAMESPACE, DREAMING_DELIVERY_GROUP_KEY);
// This allows the broadcast delivery to be delayed to apps in the Cached state.
- options.setDeferUntilActive(true);
+ options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
return options.toBundle();
}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 4d03e44..7e990c6 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -96,7 +96,11 @@
*/
public abstract int getVirtualMousePointerDisplayId();
- /** Gets the current position of the mouse cursor. */
+ /**
+ * Gets the current position of the mouse cursor.
+ *
+ * Returns NaN-s as the coordinates if the cursor is not available.
+ */
public abstract PointF getCursorPosition();
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b2b22a0..efc4f11 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2863,9 +2863,6 @@
int getPointerDisplayId();
- /** Gets the x and y coordinates of the cursor's current position. */
- PointF getCursorPosition();
-
/**
* Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event
* occurred on a window that did not have focus.
@@ -3189,7 +3186,11 @@
@Override
public PointF getCursorPosition() {
- return mWindowManagerCallbacks.getCursorPosition();
+ final float[] p = mNative.getMouseCursorPosition();
+ if (p == null || p.length != 2) {
+ throw new IllegalStateException("Failed to get mouse cursor position");
+ }
+ return new PointF(p[0], p[1]);
}
@Override
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index f873a1b..4d4a87e 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -65,6 +65,7 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.XmlUtils;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import libcore.io.Streams;
@@ -1226,9 +1227,15 @@
mContext.getSystemService(UserManager.class));
InputMethodManager inputMethodManager = Objects.requireNonNull(
mContext.getSystemService(InputMethodManager.class));
+ // Need to use InputMethodManagerInternal to call getEnabledInputMethodListAsUser()
+ // instead of using InputMethodManager which uses enforceCallingPermissions() that
+ // breaks when we are calling the method for work profile user ID since it doesn't check
+ // self permissions.
+ InputMethodManagerInternal inputMethodManagerInternal = InputMethodManagerInternal.get();
for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
int userId = userHandle.getIdentifier();
- for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
+ for (InputMethodInfo imeInfo :
+ inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
for (InputMethodSubtype imeSubtype :
inputMethodManager.getEnabledInputMethodSubtypeList(
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 22226e8..5395302d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -224,6 +224,16 @@
/** Set whether stylus button reporting through motion events should be enabled. */
void setStylusButtonMotionEventsEnabled(boolean enabled);
+ /**
+ * Get the current position of the mouse cursor.
+ *
+ * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+ *
+ * NOTE: This will grab the PointerController's lock, so we must be careful about calling this
+ * from the InputReader or Display threads, which may result in a deadlock.
+ */
+ float[] getMouseCursorPosition();
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -465,5 +475,8 @@
@Override
public native void setStylusButtonMotionEventsEnabled(boolean enabled);
+
+ @Override
+ public native float[] getMouseCursorPosition();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 9f7ff31..0ae1e80 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,17 +136,16 @@
mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
break;
case STATE_HIDE_IME:
- if (mService.mCurFocusedWindowClient != null) {
+ if (mService.hasAttachedClient()) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// IMMS only knows of focused window, not the actual IME target.
// e.g. it isn't aware of any window that has both
// NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
- // Send it to window manager to hide IME from IME target window.
- // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
- // actual IME target.
+ // Send it to window manager to hide IME from the actual IME control target
+ // of the target display.
mWindowManagerInternal.hideIme(windowToken,
- mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
+ mService.getDisplayIdToShowImeLocked(), statsToken);
} else {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 91f91f8..b336b95 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1488,16 +1488,19 @@
}
int change = isPackageDisappearing(imi.getPackageName());
- if (isPackageModified(imi.getPackageName())) {
- mAdditionalSubtypeMap.remove(imi.getId());
- AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
- mSettings.getCurrentUserId());
- }
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: "
+ imi.getComponent());
setInputMethodEnabledLocked(imi.getId(), false);
+ } else if (change == PACKAGE_UPDATING) {
+ Slog.i(TAG,
+ "Input method reinstalling, clearing additional subtypes: "
+ + imi.getComponent());
+ mAdditionalSubtypeMap.remove(imi.getId());
+ AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
+ mMethodMap,
+ mSettings.getCurrentUserId());
}
}
}
@@ -2336,6 +2339,19 @@
}
}
+ /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
+ @GuardedBy("ImfLock.class")
+ boolean hasAttachedClient() {
+ return mCurClient != null;
+ }
+
+ @VisibleForTesting
+ void setAttachedClientForTesting(@NonNull ClientState cs) {
+ synchronized (ImfLock.class) {
+ mCurClient = cs;
+ }
+ }
+
@GuardedBy("ImfLock.class")
void clearInputShownLocked() {
mVisibilityStateComputer.setInputShown(false);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index afae08d..2b5f874 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -421,6 +421,8 @@
static class Injector {
protected Context mContext;
+ private ServiceThread mHandlerThread;
+ private Handler mHandler;
public Injector(Context context) {
mContext = context;
@@ -431,14 +433,20 @@
}
public ServiceThread getServiceThread() {
- ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /*allowIo*/);
- handlerThread.start();
- return handlerThread;
+ if (mHandlerThread == null) {
+ mHandlerThread = new ServiceThread(TAG,
+ Process.THREAD_PRIORITY_BACKGROUND,
+ true /*allowIo*/);
+ mHandlerThread.start();
+ }
+ return mHandlerThread;
}
public Handler getHandler(ServiceThread handlerThread) {
- return new Handler(handlerThread.getLooper());
+ if (mHandler == null) {
+ mHandler = new Handler(handlerThread.getLooper());
+ }
+ return mHandler;
}
public LockSettingsStorage getStorage() {
@@ -519,7 +527,8 @@
public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
LockSettingsStorage storage) {
- return new RebootEscrowManager(mContext, callbacks, storage);
+ return new RebootEscrowManager(mContext, callbacks, storage,
+ getHandler(getServiceThread()));
}
public int binderGetCallingUid() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 9b42cfc..e1cd2c5 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -205,6 +205,8 @@
private final RebootEscrowKeyStoreManager mKeyStoreManager;
+ private final Handler mHandler;
+
PowerManager.WakeLock mWakeLock;
private ConnectivityManager.NetworkCallback mNetworkCallback;
@@ -399,19 +401,21 @@
}
}
- RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
- this(new Injector(context, storage), callbacks, storage);
+ RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
+ Handler handler) {
+ this(new Injector(context, storage), callbacks, storage, handler);
}
@VisibleForTesting
RebootEscrowManager(Injector injector, Callbacks callbacks,
- LockSettingsStorage storage) {
+ LockSettingsStorage storage, Handler handler) {
mInjector = injector;
mCallbacks = callbacks;
mStorage = storage;
mUserManager = injector.getUserManager();
mEventLog = injector.getEventLog();
mKeyStoreManager = injector.getKeyStoreManager();
+ mHandler = handler;
}
/** Wrapper function to set error code serialized through handler, */
@@ -937,7 +941,7 @@
private void setRebootEscrowReady(boolean ready) {
if (mRebootEscrowReady != ready) {
- mRebootEscrowListener.onPreparedForReboot(ready);
+ mHandler.post(() -> mRebootEscrowListener.onPreparedForReboot(ready));
}
mRebootEscrowReady = ready;
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3329f54..030c96e 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -261,11 +261,15 @@
}
}
- private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
+ private Condition[] getValidConditions(String pkg, Condition[] conditions) {
if (conditions == null || conditions.length == 0) return null;
final int N = conditions.length;
final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
for (int i = 0; i < N; i++) {
+ if (conditions[i] == null) {
+ Slog.w(TAG, "Ignoring null condition from " + pkg);
+ continue;
+ }
final Uri id = conditions[i].id;
if (valid.containsKey(id)) {
Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
@@ -303,7 +307,7 @@
synchronized(mMutex) {
if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
+ (conditions == null ? null : Arrays.asList(conditions)));
- conditions = removeDuplicateConditions(pkg, conditions);
+ conditions = getValidConditions(pkg, conditions);
if (conditions == null || conditions.length == 0) return;
final int N = conditions.length;
for (int i = 0; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c2e8df1..46337a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6864,7 +6864,8 @@
* A notification should be dismissible, unless it's exempted for some reason.
*/
private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
- return notification.isMediaNotification() || isEnterpriseExempted(ai);
+ return notification.isMediaNotification() || isEnterpriseExempted(ai)
+ || isCallNotification(ai.packageName, ai.uid, notification);
}
private boolean isEnterpriseExempted(ApplicationInfo ai) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c232b36..9748aba 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -41,6 +41,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -499,7 +500,7 @@
String getInstallerPackageName(@NonNull String packageName, @UserIdInt int userId);
@Nullable
- InstallSourceInfo getInstallSourceInfo(@NonNull String packageName);
+ InstallSourceInfo getInstallSourceInfo(@NonNull String packageName, @UserIdInt int userId);
@PackageManager.EnabledState
int getApplicationEnabledSetting(@NonNull String packageName, @UserIdInt int userId);
@@ -519,14 +520,15 @@
* returns false.
*/
boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
- @UserIdInt int userId);
+ @NonNull UserHandle userHandle);
/**
* @return true if the runtime app user enabled state and the install-time app manifest enabled
* state are both effectively enabled for the given app. Or if the app cannot be found,
* returns false.
*/
- boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId);
+ boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
+ @NonNull UserHandle userHandle);
@Nullable
KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5984360..acd4a96 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4982,9 +4982,11 @@
@Override
@Nullable
- public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) {
+ public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName,
+ @UserIdInt int userId) {
final int callingUid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
+ enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+ false /* checkShell */, "getInstallSourceInfo");
String installerPackageName;
String initiatingPackageName;
@@ -5129,9 +5131,10 @@
@Override
public boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
- @UserIdInt int userId) {
+ @NonNull UserHandle userHandle) {
try {
String packageName = componentInfo.packageName;
+ int userId = userHandle.getIdentifier();
int appEnabledSetting =
mSettings.getApplicationEnabledSetting(packageName, userId);
if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -5154,9 +5157,10 @@
@Override
public boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
- @UserIdInt int userId) {
+ @NonNull UserHandle userHandle) {
try {
- int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId);
+ int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName,
+ userHandle.getIdentifier());
if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
final AndroidPackage pkg = getPackage(packageName);
if (pkg == null) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index d39cac0..c29e4d7 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -463,8 +463,9 @@
@Override
@Nullable
@Deprecated
- public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) {
- return snapshot().getInstallSourceInfo(packageName);
+ public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName,
+ @UserIdInt int userId) {
+ return snapshot().getInstallSourceInfo(packageName, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/IncrementalProgressListener.java b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
index 703bbda..420e2e9 100644
--- a/services/core/java/com/android/server/pm/IncrementalProgressListener.java
+++ b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
@@ -47,6 +47,8 @@
state -> state.setLoadingProgress(progress));
// Only report the state change when loading state changes from loading to not
if (Math.abs(1.0f - progress) < 0.00000001f) {
+ mPm.commitPackageStateMutation(null, mPackageName,
+ state -> state.setLoadingCompletedTime(System.currentTimeMillis()));
// Unregister progress listener
mPm.mIncrementalManager
.unregisterLoadingProgressCallbacks(packageState.getPathString());
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fa535c3..03e0d36 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -925,7 +925,7 @@
final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId);
final boolean isUpdate = targetPackageUid != -1 || isApexSession();
final InstallSourceInfo existingInstallSourceInfo = isUpdate
- ? snapshot.getInstallSourceInfo(packageName)
+ ? snapshot.getInstallSourceInfo(packageName, userId)
: null;
final String existingInstallerPackageName = existingInstallSourceInfo != null
? existingInstallSourceInfo.getInstallingPackageName()
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d3ee52c..6bc8760 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1328,7 +1328,8 @@
throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
}
- final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName);
+ final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName,
+ userId);
final String installerPackageName;
if (installSourceInfo != null) {
if (!TextUtils.isEmpty(installSourceInfo.getInitiatingPackageName())) {
@@ -2569,7 +2570,7 @@
if (best == null || cur.priority > best.priority) {
if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(),
- UserHandle.USER_SYSTEM)) {
+ UserHandle.SYSTEM)) {
best = cur;
} else {
Slog.w(TAG, "Domain verification agent found but not enabled");
@@ -6811,7 +6812,8 @@
if (ps == null) {
return null;
}
- return new IncrementalStatesInfo(ps.isLoading(), ps.getLoadingProgress());
+ return new IncrementalStatesInfo(ps.isLoading(), ps.getLoadingProgress(),
+ ps.getLoadingCompletedTime());
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2a1172c..839ff41 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,6 +32,7 @@
import android.content.pm.UserInfo;
import android.content.pm.overlay.OverlayPaths;
import android.os.UserHandle;
+import android.os.incremental.IncrementalManager;
import android.service.pm.PackageProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -140,6 +141,7 @@
private String mPathString;
private float mLoadingProgress;
+ private long mLoadingCompletedTime;
@Nullable
private String mPrimaryCpuAbi;
@@ -630,6 +632,7 @@
super.copySettingBase(other);
mSharedUserAppId = other.mSharedUserAppId;
mLoadingProgress = other.mLoadingProgress;
+ mLoadingCompletedTime = other.mLoadingCompletedTime;
legacyNativeLibraryPath = other.legacyNativeLibraryPath;
mName = other.mName;
mRealName = other.mRealName;
@@ -1146,6 +1149,9 @@
return readUserState(userId).getSplashScreenTheme();
}
+ public boolean isIncremental() {
+ return IncrementalManager.isIncrementalPath(mPathString);
+ }
/**
* @return True if package is still being loaded, false if the package is fully loaded.
*/
@@ -1159,6 +1165,12 @@
return this;
}
+ public PackageSetting setLoadingCompletedTime(long loadingCompletedTime) {
+ mLoadingCompletedTime = loadingCompletedTime;
+ onChanged();
+ return this;
+ }
+
@NonNull
@Override
public long getVersionCode() {
@@ -1489,6 +1501,11 @@
}
@DataClass.Generated.Member
+ public long getLoadingCompletedTime() {
+ return mLoadingCompletedTime;
+ }
+
+ @DataClass.Generated.Member
public @Nullable String getCpuAbiOverride() {
return mCpuAbiOverride;
}
@@ -1563,10 +1580,10 @@
}
@DataClass.Generated(
- time = 1665779003744L,
+ time = 1678228625853L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b6557d0..94a00d6e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2902,6 +2902,8 @@
serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
}
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
+ serializer.attributeLongHex(null, "loadingCompletedTime",
+ pkg.getLoadingCompletedTime());
writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor());
@@ -2988,6 +2990,7 @@
serializer.attributeBoolean(null, "isLoading", true);
}
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
+ serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
@@ -3687,9 +3690,6 @@
ps.setAppId(sharedUserAppId);
ps.setSharedUserAppId(sharedUserAppId);
}
- final float loadingProgress =
- parser.getAttributeFloat(null, "loadingProgress", 0);
- ps.setLoadingProgress(loadingProgress);
int outerDepth = parser.getDepth();
int type;
@@ -3760,6 +3760,7 @@
long versionCode = 0;
boolean installedForceQueryable = false;
float loadingProgress = 0;
+ long loadingCompletedTime = 0;
UUID domainSetId;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
@@ -3777,6 +3778,7 @@
updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
+ loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0);
if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
primaryCpuAbiString = legacyCpuAbiString;
@@ -3939,7 +3941,8 @@
.setSecondaryCpuAbi(secondaryCpuAbiString)
.setUpdateAvailable(updateAvailable)
.setForceQueryableOverride(installedForceQueryable)
- .setLoadingProgress(loadingProgress);
+ .setLoadingProgress(loadingProgress)
+ .setLoadingCompletedTime(loadingCompletedTime);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -4900,9 +4903,11 @@
}
pw.print(prefix); pw.print(" packageSource=");
pw.println(ps.getInstallSource().mPackageSource);
- if (ps.isLoading()) {
+ if (ps.isIncremental()) {
pw.print(prefix); pw.println(" loadingProgress=" +
(int) (ps.getLoadingProgress() * 100) + "%");
+ date.setTime(ps.getLoadingCompletedTime());
+ pw.print(prefix); pw.println(" loadingCompletedTime=" + sdf.format(date));
}
if (ps.getVolumeUuid() != null) {
pw.print(prefix); pw.print(" volumeUuid=");
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index b4d467f..721ad88 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -491,8 +491,11 @@
public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
/**
- * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
- * user is not assigned to any display.
+ * Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
+ * user is not assigned to any main display.
+ *
+ * <p>In the context of multi-user multi-display, there can be multiple main displays, at most
+ * one per each zone. Main displays are where UI is launched which a user interacts with.
*
* <p>The current foreground user and its running profiles are associated with the
* {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be
@@ -503,9 +506,20 @@
*
* <p>If the user is a profile and is running, it's assigned to its parent display.
*/
+ // TODO(b/272366483) rename this method to avoid confusion with getDisplaysAssignedTOUser().
public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
/**
+ * Returns all display ids assigned to the user including {@link
+ * #assignUserToExtraDisplay(int, int) extra displays}, or {@code null} if there is no display
+ * assigned to the specified user.
+ *
+ * <p>Note that this method is different from {@link #getDisplayAssignedToUser(int)}, which
+ * returns a main display only.
+ */
+ public abstract @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId);
+
+ /**
* Returns the main user (i.e., not a profile) that is assigned to the display, or the
* {@link android.app.ActivityManager#getCurrentUser() current foreground user} if no user is
* associated with the display.
@@ -573,5 +587,6 @@
* @throws UserManager.CheckedUserOperationException if no switchable user can be found
*/
- public abstract @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException;
+ public abstract @UserIdInt int getBootUser(boolean waitUntilSet)
+ throws UserManager.CheckedUserOperationException;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3343172..fdc2aff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -161,7 +161,9 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -278,6 +280,8 @@
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
+ private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
@@ -333,6 +337,8 @@
/** Indicates that this is the 1st boot after the system user mode was changed by emulation. */
private boolean mUpdatingSystemUserMode;
+ /** Count down latch to wait while boot user is not set.*/
+ private final CountDownLatch mBootUserLatch = new CountDownLatch(1);
/**
* Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
*/
@@ -952,18 +958,62 @@
Slogf.i(LOG_TAG, "setBootUser %d", userId);
mBootUser = userId;
}
+ mBootUserLatch.countDown();
}
@Override
public @UserIdInt int getBootUser() {
checkCreateUsersPermission("Get boot user");
try {
- return mLocalService.getBootUser();
+ return getBootUserUnchecked();
} catch (UserManager.CheckedUserOperationException e) {
throw e.toServiceSpecificException();
}
}
+ private @UserIdInt int getBootUserUnchecked() throws UserManager.CheckedUserOperationException {
+ synchronized (mUsersLock) {
+ if (mBootUser != UserHandle.USER_NULL) {
+ final UserData userData = mUsers.get(mBootUser);
+ if (userData != null && userData.info.supportsSwitchToByUser()) {
+ Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+ return mBootUser;
+ } else {
+ Slogf.w(LOG_TAG,
+ "Provided boot user cannot be switched to: %d", mBootUser);
+ }
+ }
+ }
+
+ if (isHeadlessSystemUserMode()) {
+ // Return the previous foreground user, if there is one.
+ final int previousUser = getPreviousFullUserToEnterForeground();
+ if (previousUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+ return previousUser;
+ }
+ // No previous user. Return the first switchable user if there is one.
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ if (userData.info.supportsSwitchToByUser()) {
+ int firstSwitchable = userData.info.id;
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable user %d", firstSwitchable);
+ return firstSwitchable;
+ }
+ }
+ }
+ // No switchable users found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+ }
+ // Not HSUM, return system user.
+ return UserHandle.USER_SYSTEM;
+ }
+
+
@Override
public int getPreviousFullUserToEnterForeground() {
checkQueryOrCreateUsersPermission("get previous user");
@@ -1495,7 +1545,8 @@
// intentSender
unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivity(unlockIntent);
+ mContext.startActivityAsUser(
+ unlockIntent, UserHandle.of(getProfileParentIdUnchecked(userId)));
}
@Override
@@ -5814,20 +5865,24 @@
}
/**
- * @deprecated Use {@link
- * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing agents on the device with the ability to set
+ * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+ * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
*/
- @Deprecated
@Override
public Bundle getApplicationRestrictions(String packageName) {
return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
}
/**
- * @deprecated Use {@link
- * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing agents on the device with the ability to set
+ * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+ * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
*/
- @Deprecated
@Override
public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId
@@ -7139,6 +7194,11 @@
}
@Override
+ public @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+ return mUserVisibilityMediator.getDisplaysAssignedToUser(userId);
+ }
+
+ @Override
public @UserIdInt int getUserAssignedToDisplay(int displayId) {
return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
}
@@ -7182,47 +7242,29 @@
}
@Override
- public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
- synchronized (mUsersLock) {
- // TODO(b/242195409): On Automotive, block if boot user not provided.
- if (mBootUser != UserHandle.USER_NULL) {
- final UserData userData = mUsers.get(mBootUser);
- if (userData != null && userData.info.supportsSwitchToByUser()) {
- Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
- return mBootUser;
- } else {
- Slogf.w(LOG_TAG,
- "Provided boot user cannot be switched to: %d", mBootUser);
+ public @UserIdInt int getBootUser(boolean waitUntilSet)
+ throws UserManager.CheckedUserOperationException {
+ if (waitUntilSet) {
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("wait-boot-user");
+ try {
+ if (mBootUserLatch.getCount() != 0) {
+ Slogf.d(LOG_TAG,
+ "Sleeping for boot user to be set. "
+ + "Max sleep for Time: %d", BOOT_USER_SET_TIMEOUT_MS);
}
+ if (!mBootUserLatch.await(BOOT_USER_SET_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ Slogf.w(LOG_TAG, "Boot user not set. Timeout: %d",
+ BOOT_USER_SET_TIMEOUT_MS);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Slogf.w(LOG_TAG, e, "InterruptedException during wait for boot user.");
}
+ t.traceEnd();
}
- if (isHeadlessSystemUserMode()) {
- // Return the previous foreground user, if there is one.
- final int previousUser = getPreviousFullUserToEnterForeground();
- if (previousUser != UserHandle.USER_NULL) {
- Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
- return previousUser;
- }
- // No previous user. Return the first switchable user if there is one.
- synchronized (mUsersLock) {
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- final UserData userData = mUsers.valueAt(i);
- if (userData.info.supportsSwitchToByUser()) {
- int firstSwitchable = userData.info.id;
- Slogf.i(LOG_TAG,
- "Boot user is first switchable user %d", firstSwitchable);
- return firstSwitchable;
- }
- }
- }
- // No switchable users found. Uh oh!
- throw new UserManager.CheckedUserOperationException(
- "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
- }
- // Not HSUM, return system user.
- return UserHandle.USER_SYSTEM;
+ return getBootUserUnchecked();
}
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 12c9e98..2f99062 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -222,7 +222,7 @@
final Set<String> userAllowlist = getInstallablePackagesForUserId(userId);
pmInt.forEachPackageState(packageState -> {
- if (packageState.getPkg() == null) {
+ if (packageState.getPkg() == null || !packageState.isSystem()) {
return;
}
boolean install = (userAllowlist == null
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index a8615c2..3710af6 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -63,19 +63,39 @@
/**
* Class responsible for deciding whether a user is visible (or visible for a given display).
*
- * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the
+ * <p>Currently, it has 3 "modes" (set on constructor), which defines the class behavior (i.e, the
* logic that dictates the result of methods such as {@link #isUserVisible(int)} and
* {@link #isUserVisible(int, int)}):
*
* <ul>
- * <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with
- * just cluster and driver displayes, etc...), where the logic is based solely on the current
- * foreground user (and its started profiles)
- * <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on
- * automotives with passenger display. In this mode, users started in background on the secondary
- * display are stored in map.
+ * <li>default (A.K.A {@code SUSD} - Single User on Single Display): this is the most common mode
+ * (used by phones, tablets, foldables, cars with just cluster and driver displays, etc.),
+ * where just the current foreground user and its profiles are visible; hence, most methods are
+ * optimized to just check for the current user / profile. This mode is unit tested by
+ * {@link com.android.server.pm.UserVisibilityMediatorSUSDTest} and CTS tested by
+ * {@link android.multiuser.cts.UserVisibilityTest}.
+ * <li>concurrent users (A.K.A. {@code MUMD} - Multiple Users on Multiple Displays): typically
+ * used on automotive builds where the car has additional displays for passengers, it allows users
+ * to be started in the background but visible on these displays; hence, it contains additional
+ * maps to account for the visibility state. This mode is unit tested by
+ * {@link com.android.server.pm.UserVisibilityMediatorMUMDTest} and CTS tested by
+ * {@link android.multiuser.cts.UserVisibilityTest}.
+ * <li>no driver (A.K.A. {@code MUPAND} - MUltiple PAssengers, No Driver): extension of the
+ * previous mode and typically used on automotive builds where the car has additional displays for
+ * passengers but uses a secondary Android system for the back passengers, so all "human" users
+ * are started in the background (and the current foreground user is the system user), hence the
+ * "no driver name". This mode is unit tested by
+ * {@link com.android.server.pm.UserVisibilityMediatorMUPANDTest} and CTS tested by
+ * {@link android.multiuser.cts.UserVisibilityVisibleBackgroundUsersOnDefaultDisplayTest}.
* </ul>
*
+ * <p>When you make changes in this class, you should run at least the 3 unit tests and
+ * {@link android.multiuser.cts.UserVisibilityTest} (which actually applies for all modes); for
+ * example, by calling {@code atest UserVisibilityMediatorSUSDTest UserVisibilityMediatorMUMDTest
+ * UserVisibilityMediatorMUPANDTest UserVisibilityTest}. Ideally, you should run the other 2 CTS
+ * tests as well (you can emulate these modes using {@code adb} commands; their javadoc provides
+ * instructions on how to do so).
+ *
* <p>This class is thread safe.
*/
public final class UserVisibilityMediator implements Dumpable {
@@ -786,6 +806,49 @@
}
}
+ /** See {@link UserManagerInternal#getDisplaysAssignedToUser(int)}. */
+ @Nullable
+ public int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+ int mainDisplayId = getDisplayAssignedToUser(userId);
+ if (mainDisplayId == INVALID_DISPLAY) {
+ // The user will not have any extra displays if they have no main display.
+ // Return null if no display is assigned to the user.
+ if (DBG) {
+ Slogf.d(TAG, "getDisplaysAssignedToUser(): returning null"
+ + " because there is no display assigned to user %d", userId);
+ }
+ return null;
+ }
+
+ synchronized (mLock) {
+ if (mExtraDisplaysAssignedToUsers == null
+ || mExtraDisplaysAssignedToUsers.size() == 0) {
+ return new int[]{mainDisplayId};
+ }
+
+ int count = 0;
+ int[] displayIds = new int[mExtraDisplaysAssignedToUsers.size() + 1];
+ displayIds[count++] = mainDisplayId;
+ for (int i = 0; i < mExtraDisplaysAssignedToUsers.size(); ++i) {
+ if (mExtraDisplaysAssignedToUsers.valueAt(i) == userId) {
+ displayIds[count++] = mExtraDisplaysAssignedToUsers.keyAt(i);
+ }
+ }
+ // Return the array if the array length happens to be correct.
+ if (displayIds.length == count) {
+ return displayIds;
+ }
+
+ // Copy the results to a new array with the exact length. The size of displayIds[] is
+ // initialized to `1 + mExtraDisplaysAssignedToUsers.size()`, which is usually larger
+ // than the actual length, because mExtraDisplaysAssignedToUsers contains displayIds for
+ // other users. Therefore, we need to copy to a new array with the correct length.
+ int[] results = new int[count];
+ System.arraycopy(displayIds, 0, results, 0, count);
+ return results;
+ }
+ }
+
/**
* See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
*/
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 5b967ec..f340f93 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -26,7 +26,6 @@
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
@@ -408,7 +407,7 @@
final int numRequiredVerifierPackages = requiredVerifierPackages.size();
for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) {
if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i),
- SYSTEM_UID)) {
+ verifierUser)) {
Slog.w(TAG,
"Required verifier: " + requiredVerifierPackages.get(i) + " is disabled");
requiredVerifierPackages.remove(i);
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index 2f4c0277..3a0ff27 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -81,6 +81,8 @@
float getLoadingProgress();
+ long getLoadingCompletedTime();
+
@NonNull
PackageKeySetData getKeySetData();
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 5947d47..8125b0f 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -274,6 +274,15 @@
@NonNull
@Override
+ public PackageStateWrite setLoadingCompletedTime(long loadingCompletedTime) {
+ if (mState != null) {
+ mState.setLoadingCompletedTime(loadingCompletedTime);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
public PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo) {
if (mState != null) {
mState.getTransientState().setOverrideSeInfo(newSeInfo);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index c610c02..55d96f3 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -53,6 +53,9 @@
PackageStateWrite setLoadingProgress(float progress);
@NonNull
+ PackageStateWrite setLoadingCompletedTime(long loadingCompletedTime);
+
+ @NonNull
PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
@NonNull
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 8d7f782..3644054 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -21,7 +21,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -101,8 +104,10 @@
private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
"FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
- private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL =
- "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL";
+ private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
+ "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
+ private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
+ "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -162,9 +167,12 @@
case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
break;
- case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL:
- flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL;
+ case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
+ flags |= DeviceState
+ .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
break;
+ case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
+ flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
@@ -210,6 +218,9 @@
@GuardedBy("mLock")
private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
+ @GuardedBy("mLock")
+ private boolean mPowerSaveModeEnabled;
+
private DeviceStateProviderImpl(@NonNull Context context,
@NonNull List<DeviceState> deviceStates,
@NonNull List<Conditions> stateConditions) {
@@ -224,14 +235,32 @@
setStateConditions(deviceStates, stateConditions);
- // If any of the device states are thermal sensitive, i.e. it should be disabled when the
- // device is overheating, then we will update the list of supported states when thermal
- // status changes.
- if (hasThermalSensitiveState(deviceStates)) {
- PowerManager powerManager = context.getSystemService(PowerManager.class);
- if (powerManager != null) {
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ if (powerManager != null) {
+ // If any of the device states are thermal sensitive, i.e. it should be disabled when
+ // the device is overheating, then we will update the list of supported states when
+ // thermal status changes.
+ if (hasThermalSensitiveState(deviceStates)) {
powerManager.addThermalStatusListener(this);
}
+
+ // If any of the device states are power sensitive, i.e. it should be disabled when
+ // power save mode is enabled, then we will update the list of supported states when
+ // power save mode is toggled.
+ if (hasPowerSaveSensitiveState(deviceStates)) {
+ IntentFilter filter = new IntentFilter(
+ PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals(
+ intent.getAction())) {
+ onPowerSaveModeChanged(powerManager.isPowerSaveMode());
+ }
+ }
+ };
+ mContext.registerReceiver(receiver, filter);
+ }
}
}
@@ -382,7 +411,11 @@
for (DeviceState deviceState : mOrderedStates) {
if (isThermalStatusCriticalOrAbove(mThermalStatus)
&& deviceState.hasFlag(
- DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ continue;
+ }
+ if (mPowerSaveModeEnabled && deviceState.hasFlag(
+ DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
continue;
}
supportedStates.add(deviceState);
@@ -674,6 +707,18 @@
}
}
+ @VisibleForTesting
+ void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) {
+ synchronized (mLock) {
+ if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) {
+ mPowerSaveModeEnabled = isPowerSaveModeEnabled;
+ notifySupportedStatesChanged(
+ isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED
+ : SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED);
+ }
+ }
+ }
+
@Override
public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
int previousThermalStatus;
@@ -709,7 +754,16 @@
private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
for (DeviceState state : deviceStates) {
- if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) {
+ for (int i = 0; i < deviceStates.size(); i++) {
+ if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4ec8afd..ea53ea5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -647,6 +647,9 @@
private boolean mLockNowPending = false;
+ // Timeout for showing the keyguard after the screen is on, in case no "ready" is received.
+ private int mKeyguardDrawnTimeout = 1000;
+
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5;
@@ -2236,6 +2239,8 @@
}
});
+ mKeyguardDrawnTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
new StateCallback() {
@Override
@@ -4981,7 +4986,7 @@
final boolean bootCompleted =
LocalServices.getService(SystemServiceManager.class).isBootCompleted();
// Set longer timeout if it has not booted yet to prevent showing empty window.
- return bootCompleted ? 1000 : 5000;
+ return bootCompleted ? mKeyguardDrawnTimeout : 5000;
}
// Called on the DisplayManager's DisplayPowerController thread.
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index da7aaa4..d0ed9bf 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -241,7 +241,7 @@
UUID.randomUUID().toString(),
Intent.ACTION_SCREEN_ON);
// This allows the broadcast delivery to be delayed to apps in the Cached state.
- options.setDeferUntilActive(true);
+ options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
return options.toBundle();
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index bc23020..661715c 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.UidTraffic;
@@ -500,14 +501,6 @@
}
- /** Handles calls to AlarmManager */
- public interface AlarmInterface {
- /** Schedule an RTC alarm */
- void schedule(long rtcTimeMs, long windowLengthMs);
- /** Cancel the previously scheduled alarm */
- void cancel();
- }
-
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
private final Runnable mDeferSetCharging = new Runnable() {
@@ -1569,8 +1562,15 @@
@GuardedBy("this")
protected BatteryStatsConfig mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
- @VisibleForTesting
- protected AlarmInterface mLongPlugInAlarmInterface = null;
+ @GuardedBy("this")
+ private AlarmManager mAlarmManager = null;
+
+ private final AlarmManager.OnAlarmListener mLongPlugInAlarmHandler = () ->
+ mHandler.post(() -> {
+ synchronized (BatteryStatsImpl.this) {
+ maybeResetWhilePluggedInLocked();
+ }
+ });
/*
* Holds a SamplingTimer associated with each Resource Power Manager state and voter,
@@ -11061,18 +11061,6 @@
}
/**
- * Injects an AlarmInterface for the long plug in alarm.
- */
- public void setLongPlugInAlarmInterface(AlarmInterface longPlugInAlarmInterface) {
- synchronized (this) {
- mLongPlugInAlarmInterface = longPlugInAlarmInterface;
- if (mBatteryPluggedIn) {
- scheduleNextResetWhilePluggedInCheck();
- }
- }
- }
-
- /**
* Starts tracking CPU time-in-state for threads of the system server process,
* keeping a separate account of threads receiving incoming binder calls.
*/
@@ -14173,6 +14161,7 @@
/**
* Might reset battery stats if conditions are met. Assumed the device is currently plugged in.
*/
+ @VisibleForTesting
@GuardedBy("this")
public void maybeResetWhilePluggedInLocked() {
final long elapsedRealtimeMs = mClock.elapsedRealtime();
@@ -14189,28 +14178,31 @@
@GuardedBy("this")
private void scheduleNextResetWhilePluggedInCheck() {
- if (mLongPlugInAlarmInterface != null) {
- final long timeoutMs = mClock.currentTimeMillis()
- + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
- * DateUtils.HOUR_IN_MILLIS;
- Calendar nextAlarm = Calendar.getInstance();
- nextAlarm.setTimeInMillis(timeoutMs);
+ if (mAlarmManager == null) return;
+ final long timeoutMs = mClock.currentTimeMillis()
+ + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
+ * DateUtils.HOUR_IN_MILLIS;
+ Calendar nextAlarm = Calendar.getInstance();
+ nextAlarm.setTimeInMillis(timeoutMs);
- // Find the 2 AM the same day as the end of the minimum duration.
- // This logic does not handle a Daylight Savings transition, or a timezone change
- // while the alarm has been set. The need to reset after a long period while plugged
- // in is not strict enough to warrant a well architected out solution.
- nextAlarm.set(Calendar.MILLISECOND, 0);
- nextAlarm.set(Calendar.SECOND, 0);
- nextAlarm.set(Calendar.MINUTE, 0);
- nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
- long nextTimeMs = nextAlarm.getTimeInMillis();
- if (nextTimeMs < timeoutMs) {
- // The 2AM on the day of the timeout, move on the next day.
- nextTimeMs += DateUtils.DAY_IN_MILLIS;
- }
- mLongPlugInAlarmInterface.schedule(nextTimeMs, DateUtils.HOUR_IN_MILLIS);
+ // Find the 2 AM the same day as the end of the minimum duration.
+ // This logic does not handle a Daylight Savings transition, or a timezone change
+ // while the alarm has been set. The need to reset after a long period while plugged
+ // in is not strict enough to warrant a well architected out solution.
+ nextAlarm.set(Calendar.MILLISECOND, 0);
+ nextAlarm.set(Calendar.SECOND, 0);
+ nextAlarm.set(Calendar.MINUTE, 0);
+ nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
+ long possibleNextTimeMs = nextAlarm.getTimeInMillis();
+ if (possibleNextTimeMs < timeoutMs) {
+ // The 2AM on the day of the timeout, move on the next day.
+ possibleNextTimeMs += DateUtils.DAY_IN_MILLIS;
}
+ final long nextTimeMs = possibleNextTimeMs;
+ final AlarmManager am = mAlarmManager;
+ mHandler.post(() -> am.setWindow(AlarmManager.RTC, nextTimeMs,
+ DateUtils.HOUR_IN_MILLIS,
+ TAG, mLongPlugInAlarmHandler, mHandler));
}
@@ -14339,8 +14331,12 @@
initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
}
mBatteryPluggedIn = false;
- if (mLongPlugInAlarmInterface != null) {
- mLongPlugInAlarmInterface.cancel();
+ if (mAlarmManager != null) {
+ final AlarmManager am = mAlarmManager;
+ mHandler.post(() -> {
+ // No longer plugged in. Cancel the long plug in alarm.
+ am.cancel(mLongPlugInAlarmHandler);
+ });
}
mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
@@ -15178,6 +15174,14 @@
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
registerUsbStateReceiver(context);
+
+ synchronized (this) {
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ if (mBatteryPluggedIn) {
+ // Already plugged in. Schedule the long plug in alarm.
+ scheduleNextResetWhilePluggedInCheck();
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 8e8abf6..96f4a01 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -250,15 +250,7 @@
service.checkRecognitionSupport(recognizerIntent, attributionSource, callback));
}
- void triggerModelDownload(Intent recognizerIntent, AttributionSource attributionSource) {
- if (!mConnected) {
- Slog.e(TAG, "#downloadModel failed due to connection.");
- return;
- }
- run(service -> service.triggerModelDownload(recognizerIntent, attributionSource));
- }
-
- void setModelDownloadListener(
+ void triggerModelDownload(
Intent recognizerIntent,
AttributionSource attributionSource,
IModelDownloadListener listener) {
@@ -266,25 +258,12 @@
try {
listener.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to report the connection broke to the caller.", e);
+ Slog.w(TAG, "#downloadModel failed due to connection.", e);
e.printStackTrace();
}
return;
}
-
- run(service ->
- service.setModelDownloadListener(recognizerIntent, attributionSource, listener));
- }
-
- void clearModelDownloadListener(
- Intent recognizerIntent,
- AttributionSource attributionSource) {
- if (!mConnected) {
- return;
- }
-
- run(service ->
- service.clearModelDownloadListener(recognizerIntent, attributionSource));
+ run(service -> service.triggerModelDownload(recognizerIntent, attributionSource, listener));
}
void shutdown(IBinder clientToken) {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bc73db1..bff6d50 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -193,25 +193,11 @@
@Override
public void triggerModelDownload(
Intent recognizerIntent,
- AttributionSource attributionSource) {
- service.triggerModelDownload(recognizerIntent, attributionSource);
- }
-
- @Override
- public void setModelDownloadListener(
- Intent recognizerIntent,
AttributionSource attributionSource,
- IModelDownloadListener listener) throws RemoteException {
- service.setModelDownloadListener(
+ IModelDownloadListener listener) {
+ service.triggerModelDownload(
recognizerIntent, attributionSource, listener);
}
-
- @Override
- public void clearModelDownloadListener(
- Intent recognizerIntent,
- AttributionSource attributionSource) throws RemoteException {
- service.clearModelDownloadListener(recognizerIntent, attributionSource);
- }
});
} catch (RemoteException e) {
Slog.e(TAG, "Error creating a speech recognition session", e);
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 601d0e2..3d8f538 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -34,6 +34,7 @@
import static android.net.NetworkTemplate.OEM_MANAGED_PRIVATE;
import static android.os.Debug.getIonHeapsSizeKb;
import static android.os.Process.LAST_SHARED_APPLICATION_GID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Process.getUidForPid;
import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
@@ -89,8 +90,10 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -4213,20 +4216,26 @@
int pullInstalledIncrementalPackagesLocked(int atomTag, List<StatsEvent> pulledData) {
final PackageManager pm = mContext.getPackageManager();
+ final PackageManagerInternal pmIntenral =
+ LocalServices.getService(PackageManagerInternal.class);
if (!pm.hasSystemFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY)) {
// Incremental is not enabled on this device. The result list will be empty.
return StatsManager.PULL_SUCCESS;
}
final long token = Binder.clearCallingIdentity();
try {
- int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+ final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
for (int userId : userIds) {
- List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(0, userId);
+ final List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(
+ 0, userId);
for (PackageInfo pi : installedPackages) {
if (IncrementalManager.isIncrementalPath(
pi.applicationInfo.getBaseCodePath())) {
+ final IncrementalStatesInfo info = pmIntenral.getIncrementalStatesInfo(
+ pi.packageName, SYSTEM_UID, userId);
pulledData.add(
- FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid));
+ FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid,
+ info.isLoading(), info.getLoadingCompletedTime()));
}
}
}
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 ed91775..2d3928c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1019,6 +1019,7 @@
int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
// If the desired frontend id was specified, we only need to check the frontend.
boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
for (FrontendResource fr : getFrontendResources().values()) {
@@ -1048,6 +1049,8 @@
if (currentLowestPriority > priority) {
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(fr.getOwnerClientId())).getProcessId());
}
}
}
@@ -1063,7 +1066,8 @@
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
- && (requestClient.getPriority() > currentLowestPriority)) {
+ && ((requestClient.getPriority() > currentLowestPriority) || (
+ (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
if (!reclaimResource(
getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
@@ -1182,6 +1186,7 @@
int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
for (LnbResource lnb : getLnbResources().values()) {
if (!lnb.isInUse()) {
// Grant the unused lnb with lower handle first
@@ -1194,6 +1199,8 @@
if (currentLowestPriority > priority) {
inUseLowestPriorityLnbHandle = lnb.getHandle();
currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
}
}
}
@@ -1208,7 +1215,8 @@
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
- && (requestClient.getPriority() > currentLowestPriority)) {
+ && ((requestClient.getPriority() > currentLowestPriority) || (
+ (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
return false;
@@ -1240,6 +1248,7 @@
int lowestPriorityOwnerId = -1;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
if (!cas.isFullyUsed()) {
casSessionHandle[0] = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
@@ -1252,12 +1261,15 @@
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(ownerId)).getProcessId());
}
}
// When all the Cas sessions are occupied, reclaim the lowest priority client if the
// request client has higher priority.
- if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+ if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
if (!reclaimResource(lowestPriorityOwnerId,
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
return false;
@@ -1289,6 +1301,7 @@
int lowestPriorityOwnerId = -1;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
if (!ciCam.isFullyUsed()) {
ciCamHandle[0] = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
@@ -1301,12 +1314,16 @@
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(ownerId)).getProcessId());
}
}
// When all the CiCam sessions are occupied, reclaim the lowest priority client if the
// request client has higher priority.
- if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+ if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
if (!reclaimResource(lowestPriorityOwnerId,
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
return false;
@@ -1424,6 +1441,7 @@
int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
// If the desired demux id was specified, we only need to check the demux.
boolean hasDesiredDemuxCap = request.desiredFilterTypes
!= DemuxFilterMainType.UNDEFINED;
@@ -1448,6 +1466,8 @@
// update lowest priority
if (currentLowestPriority > priority) {
currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(dr.getOwnerClientId())).getProcessId());
shouldUpdate = true;
}
// update smallest caps
@@ -1473,7 +1493,8 @@
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
- && (requestClient.getPriority() > currentLowestPriority)) {
+ && ((requestClient.getPriority() > currentLowestPriority) || (
+ (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
if (!reclaimResource(
getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d108f0d..f14a432 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -251,11 +251,6 @@
// {@link #restartActivityProcessIfVisible}.
restartingName = r.app.mName;
restartingUid = r.app.mUid;
- // Make EnsureActivitiesVisibleHelper#makeVisibleAndRestartIfNeeded not skip
- // restarting non-top activity.
- if (r != r.getTask().topRunningActivity()) {
- r.setVisibleRequested(false);
- }
}
r.activityStopped(icicle, persistentState, description);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d15d094..e21c156 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4135,9 +4135,7 @@
} else if (!mVisibleRequested && launchCount > 2
&& lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
// We have launched this activity too many times since it was able to run, so give up
- // and remove it. (Note if the activity is visible, we don't remove the record. We leave
- // the dead window on the screen but the process will not be restarted unless user
- // explicitly tap on it.)
+ // and remove it.
remove = true;
} else {
// The process may be gone, but the activity lives on!
@@ -4159,11 +4157,6 @@
if (DEBUG_APP) {
Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
}
- // Set nowVisible to previous visible state. If the app was visible while it died, we
- // leave the dead window on screen so it's basically visible. This is needed when user
- // later tap on the dead window, we need to stop other apps when user transfers focus
- // to the restarted activity.
- nowVisible = mVisibleRequested;
}
// upgrade transition trigger to task if this is the last activity since it means we are
// closing the task.
@@ -4364,17 +4357,6 @@
@Override
void addWindow(WindowState w) {
super.addWindow(w);
-
- boolean gotReplacementWindow = false;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState candidate = mChildren.get(i);
- gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
- }
-
- // if we got a replacement window, reset the timeout to give drawing more time
- if (gotReplacementWindow) {
- mWmService.scheduleWindowReplacementTimeouts(this);
- }
checkKeyguardFlagsChanged();
}
@@ -4389,12 +4371,6 @@
updateLetterboxSurface(child);
}
- void onWindowReplacementTimeout() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- (mChildren.get(i)).onWindowReplacementTimeout();
- }
- }
-
void setAppLayoutChanges(int changes, String reason) {
if (!mChildren.isEmpty()) {
final DisplayContent dc = getDisplayContent();
@@ -4405,15 +4381,6 @@
}
}
- void removeReplacedWindowIfNeeded(WindowState replacement) {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = mChildren.get(i);
- if (win.removeReplacedWindowIfNeeded(replacement)) {
- return;
- }
- }
- }
-
private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) {
final WindowState tStartingWindow = fromActivity.mStartingWindow;
if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
@@ -5232,6 +5199,11 @@
Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
return;
}
+ if (visible == mVisibleRequested && visible == mVisible
+ && mTransitionController.isShellTransitionsEnabled()) {
+ // For shell transition, it is no-op if there is no state change.
+ return;
+ }
if (visible) {
mDeferHidingClient = false;
}
@@ -5270,13 +5242,18 @@
// Before setting mVisibleRequested so we can track changes.
boolean isCollecting = false;
+ boolean inFinishingTransition = false;
if (mTransitionController.isShellTransitionsEnabled()) {
isCollecting = mTransitionController.isCollecting();
if (isCollecting) {
mTransitionController.collect(this);
} else {
- Slog.e(TAG, "setVisibility=" + visible + " while transition is not collecting "
- + this + " caller=" + Debug.getCallers(8));
+ inFinishingTransition = mTransitionController.inFinishingTransition(this);
+ if (!inFinishingTransition) {
+ Slog.e(TAG, "setVisibility=" + visible
+ + " while transition is not collecting or finishing "
+ + this + " caller=" + Debug.getCallers(8));
+ }
}
}
@@ -5290,10 +5267,6 @@
mLastDeferHidingClient = deferHidingClient;
if (!visible) {
- // If the app is dead while it was visible, we kept its dead window on screen.
- // Now that the app is going invisible, we can remove it. It will be restarted
- // if made visible again.
- removeDeadWindows();
// If this activity is about to finish/stopped and now becomes invisible, remove it
// from the unknownApp list in case the activity does not want to draw anything, which
// keep the user waiting for the next transition to start.
@@ -5357,6 +5330,10 @@
}
return;
}
+ if (inFinishingTransition) {
+ // Let the finishing transition commit the visibility.
+ return;
+ }
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (deferCommitVisibilityChange(visible)) {
@@ -5630,10 +5607,7 @@
// * activity is transitioning visibility state
// * or the activity was marked as hidden and is exiting before we had a chance to play the
// transition animation
- // * or this is an opening app and windows are being replaced (e.g. freeform window to
- // normal window).
- return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting)
- || (visible && forAllWindows(WindowState::waitingForReplacement, true));
+ return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
}
/**
@@ -6642,9 +6616,6 @@
// stop tracking
mSplashScreenStyleSolidColor = true;
- // We now have a good window to show, remove dead placeholders
- removeDeadWindows();
-
if (mStartingWindow != null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
+ ": first real window is shown, no animation", win.mToken);
@@ -7380,49 +7351,6 @@
}
}
- void removeDeadWindows() {
- for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
- WindowState win = mChildren.get(winNdx);
- if (win.mAppDied) {
- ProtoLog.w(WM_DEBUG_ADD_REMOVE,
- "removeDeadWindows: %s", win);
- // Set mDestroying, we don't want any animation or delayed removal here.
- win.mDestroying = true;
- // Also removes child windows.
- win.removeIfPossible();
- }
- }
- }
-
- void setWillReplaceWindows(boolean animate) {
- ProtoLog.d(WM_DEBUG_ADD_REMOVE,
- "Marking app token %s with replacing windows.", this);
-
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- w.setWillReplaceWindow(animate);
- }
- }
-
- void setWillReplaceChildWindows() {
- ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Marking app token %s"
- + " with replacing child windows.", this);
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- w.setWillReplaceChildWindows();
- }
- }
-
- void clearWillReplaceWindows() {
- ProtoLog.d(WM_DEBUG_ADD_REMOVE,
- "Resetting app token %s of replacing window marks.", this);
-
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- w.clearWillReplaceWindow();
- }
- }
-
void requestUpdateWallpaperIfNeeded() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState w = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 5d038dc..be7d9b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -92,6 +92,7 @@
} else {
mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
}
+ mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
return mInputWindowHandleWrapper;
}
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 1f7af41..19d8129 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
static final String DOC_LINK = "go/android-asm";
/** Used to determine which version of the ASM logic was used in logs while we iterate */
- static final int ASM_VERSION = 6;
+ static final int ASM_VERSION = 7;
private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
@@ -89,6 +89,9 @@
if (flagEnabled) {
String[] packageNames = sPm.getPackagesForUid(uid);
+ if (packageNames == null) {
+ return true;
+ }
for (int i = 0; i < packageNames.length; i++) {
if (sExcludedPackageNames.contains(packageNames[i])) {
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 89cd7db..ce29564 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1680,6 +1680,11 @@
targetTask.removeImmediately("bulky-task");
return START_ABORTED;
}
+ // When running transient transition, the transient launch target should keep on top.
+ // So disallow the transient hide activity to move itself to front, e.g. trampoline.
+ if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+ mAvoidMoveToFront = true;
+ }
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
}
@@ -1796,7 +1801,7 @@
// root-task to the will not update the focused root-task. If starting the new
// activity now allows the task root-task to be focusable, then ensure that we
// now update the focused root-task accordingly.
- if (mTargetRootTask.isTopActivityFocusable()
+ if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
mTargetRootTask.moveToFront("startActivityInner");
}
@@ -2269,13 +2274,14 @@
*/
private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int realCallingUid,
int startingUid, int launchFlags) {
- if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK) {
- // Launch is from the same task, so must be a top or privileged UID
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
+ || mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ // Launch is from the same task, (a top or privileged UID), or is directly privileged.
return;
}
- Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing
- && (ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid));
+ Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
+ ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
// Return early if we know for sure we won't need to clear any activities by just checking
// the top activity.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a0a2557..eaf5583 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1666,7 +1666,7 @@
String callerActivityClassName) {
// We may have already checked that the callingUid has additional clearTask privileges, and
// cleared the calling identify. If so, we infer we do not need further restrictions here.
- if (callingUid == SYSTEM_UID) {
+ if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
return;
}
@@ -1772,13 +1772,19 @@
return new Pair<>(true, true);
}
+ // Always allow actual top activity to clear task
+ ActivityRecord topActivity = task.getTopMostActivity();
+ if (topActivity != null && topActivity.isUid(uid)) {
+ return new Pair<>(true, true);
+ }
+
// Consider the source activity, whether or not it is finishing. Do not consider any other
// finishing activity.
Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
|| (!ar.finishing && !ar.isAlwaysOnTop());
// Check top of stack (or the first task fragment for embedding).
- ActivityRecord topActivity = task.getActivity(topOfStackPredicate);
+ topActivity = task.getActivity(topOfStackPredicate);
if (topActivity == null) {
return new Pair<>(false, false);
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f9f972c..b67bc62 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -862,8 +862,16 @@
WindowContainer target, boolean isOpen) {
final BackWindowAnimationAdaptor adaptor =
new BackWindowAnimationAdaptor(target, isOpen);
- target.startAnimation(target.getPendingTransaction(), adaptor, false /* hidden */,
- ANIMATION_TYPE_PREDICT_BACK);
+ final SurfaceControl.Transaction pt = target.getPendingTransaction();
+ target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
+ // Workaround to show TaskFragment which can be hide in Transitions and won't show
+ // during isAnimating.
+ if (isOpen && target.asActivityRecord() != null) {
+ final TaskFragment fragment = target.asActivityRecord().getTaskFragment();
+ if (fragment != null) {
+ pt.show(fragment.mSurfaceControl);
+ }
+ }
return adaptor;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 0b28ba2..bc9efc8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -377,15 +377,10 @@
mDragWindowHandle.ownerUid = MY_UID;
mDragWindowHandle.scaleFactor = 1.0f;
- // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be
- // focusable, which allows the system to consume keys when dragging is active. This can
- // also be used to modify the drag state on key press. For example, cancel drag on
- // escape key.
// InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
// touches to pass through to windows underneath. This allows user to interact with the
// UI to navigate while dragging.
- mDragWindowHandle.inputConfig =
- InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY;
+ mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
// The drag window cannot receive new touches.
mDragWindowHandle.touchableRegion.setEmpty();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index dde89e9..9cc311d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -193,7 +193,7 @@
}
if (!r.attachedToProcess()) {
- makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
+ makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
resumeTopActivity && isTop, r);
} else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
@@ -243,15 +243,7 @@
}
private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
- boolean isTop, boolean andResume, ActivityRecord r) {
- // We need to make sure the app is running if it's the top, or it is just made visible from
- // invisible. If the app is already visible, it must have died while it was visible. In this
- // case, we'll show the dead window but will not restart the app. Otherwise we could end up
- // thrashing.
- if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
- return;
- }
-
+ boolean andResume, ActivityRecord r) {
// This activity needs to be visible, but isn't even running...
// get it started and resume if no other root task in this root task is resumed.
if (DEBUG_VISIBILITY) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1e9d451..0a47fe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,7 +25,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.graphics.PointF;
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
@@ -221,11 +220,6 @@
}
@Override
- public PointF getCursorPosition() {
- return mService.getLatestMousePosition();
- }
-
- @Override
public void onPointerDownOutsideFocus(IBinder touchedToken) {
mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f355f08..5db39fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -997,7 +997,8 @@
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- return isSurfaceVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
+ return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+ && mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox background.
@@ -1104,7 +1105,7 @@
// for all corners for consistency and pick a minimal bottom one for consistency with a
// taskbar rounded corners.
int getRoundedCornersRadius(final WindowState mainWindow) {
- if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ if (!requiresRoundedCorners(mainWindow)) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b2a4df1..fda2125 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -44,7 +44,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -432,13 +431,6 @@
}
};
- private static final Consumer<WindowState> sRemoveReplacedWindowsConsumer = w -> {
- final ActivityRecord activity = w.mActivityRecord;
- if (activity != null) {
- activity.removeReplacedWindowIfNeeded(w);
- }
- };
-
RootWindowContainer(WindowManagerService service) {
super(service);
mHandler = new MyHandler(service.mH.getLooper());
@@ -662,17 +654,6 @@
forAllWindows(mCloseSystemDialogsConsumer, false /* traverseTopToBottom */);
}
- void removeReplacedWindows() {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION removeReplacedWindows");
- mWmService.openSurfaceTransaction();
- try {
- forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
- } finally {
- mWmService.closeSurfaceTransaction("removeReplacedWindows");
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION removeReplacedWindows");
- }
- }
-
boolean hasPendingLayoutChanges(WindowAnimator animator) {
boolean hasChanges = false;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ce9bff8..b1ca72b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -232,11 +232,6 @@
}
@Override
- public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
- mService.setWillReplaceWindows(appToken, childrenOnly);
- }
-
- @Override
public boolean cancelDraw(IWindow window) {
return mService.cancelDraw(this, window);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 969f65c..6882e4c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -866,23 +866,8 @@
return false;
}
- final int toRootTaskWindowingMode = toRootTask.getWindowingMode();
final ActivityRecord topActivity = getTopNonFinishingActivity();
- final boolean mightReplaceWindow = topActivity != null
- && replaceWindowsOnTaskMove(getWindowingMode(), toRootTaskWindowingMode);
- if (mightReplaceWindow) {
- // We are about to relaunch the activity because its configuration changed due to
- // being maximized, i.e. size change. The activity will first remove the old window
- // and then add a new one. This call will tell window manager about this, so it can
- // preserve the old window until the new one is drawn. This prevents having a gap
- // between the removal and addition, in which no window is visible. We also want the
- // entrance of the new window to be properly animated.
- // Note here we always set the replacing window first, as the flags might be needed
- // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
- windowManager.setWillReplaceWindow(topActivity.token, animate);
- }
-
mAtmService.deferWindowLayout();
boolean kept = true;
try {
@@ -926,17 +911,10 @@
mAtmService.continueWindowLayout();
}
- if (mightReplaceWindow) {
- // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
- // window), we need to clear the replace window settings. Otherwise, we schedule a
- // timeout to remove the old window if the replacing window is not coming in time.
- windowManager.scheduleClearWillReplaceWindows(topActivity.token, !kept);
- }
-
if (!deferResume) {
// The task might have already been running and its visibility needs to be synchronized
// with the visibility of the root task / windows.
- root.ensureActivitiesVisible(null, 0, !mightReplaceWindow);
+ root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
root.resumeFocusedTasksTopActivities();
}
@@ -947,17 +925,6 @@
return (preferredRootTask == toRootTask);
}
- /**
- * @return {@code true} if the windows of tasks being moved to the target root task from the
- * source root task should be replaced, meaning that window manager will keep the old window
- * around until the new is ready.
- */
- private static boolean replaceWindowsOnTaskMove(
- int sourceWindowingMode, int targetWindowingMode) {
- return sourceWindowingMode == WINDOWING_MODE_FREEFORM
- || targetWindowingMode == WINDOWING_MODE_FREEFORM;
- }
-
void touchActiveTime() {
lastActiveTime = SystemClock.elapsedRealtime();
}
@@ -3398,8 +3365,10 @@
final boolean isTopActivityResumed = top != null
&& top.getOrganizedTask() == this && top.isState(RESUMED);
- // Whether the direct top activity is in size compat mode on foreground.
- info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
+ final boolean isTopActivityVisible = top != null
+ && top.getOrganizedTask() == this && top.isVisible();
+ // Whether the direct top activity is in size compat mode
+ info.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
if (info.topActivityInSizeCompat
&& mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
// We hide the restart button in case of transparent activities.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2ddb307..7c57dc1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1012,6 +1012,10 @@
if (isTopActivityLaunchedBehind()) {
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
+ final Task thisTask = asTask();
+ if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
+ return TASK_FRAGMENT_VISIBILITY_VISIBLE;
+ }
boolean gotTranslucentFullscreen = false;
boolean gotTranslucentAdjacent = false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 606f011..370d304 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -194,6 +194,13 @@
*/
private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
+ /**
+ * The tasks that may be occluded by the transient activity. Assume the task stack is
+ * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
+ * task, and [B, C] are the transient-hide tasks.
+ */
+ private ArrayList<Task> mTransientHideTasks;
+
/** Custom activity-level animation options and callbacks. */
private TransitionInfo.AnimationOptions mOverrideOptions;
private IRemoteCallback mClientAnimationStartCallback = null;
@@ -265,35 +272,51 @@
void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
if (mTransientLaunches == null) {
mTransientLaunches = new ArrayMap<>();
+ mTransientHideTasks = new ArrayList<>();
}
mTransientLaunches.put(activity, restoreBelow);
setTransientLaunchToChanges(activity);
if (restoreBelow != null) {
- final ChangeInfo info = mChanges.get(restoreBelow);
- if (info != null) {
- info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ // Collect all visible activities which can be occluded by the transient activity to
+ // make sure they are in the participants so their visibilities can be updated when
+ // finishing transition.
+ ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
+ if (t.isVisibleRequested() && !t.isAlwaysOnTop()
+ && !t.getWindowConfiguration().tasksAreFloating()) {
+ if (t.isRootTask()) {
+ mTransientHideTasks.add(t);
+ }
+ if (t.isLeafTask()) {
+ t.forAllActivities(r -> {
+ if (r.isVisibleRequested()) {
+ collect(r);
+ }
+ });
+ }
+ }
+ return t == restoreBelow;
+ });
+ // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
+ // so ChangeInfo#hasChanged() can return true to report the transition info.
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = mChanges.keyAt(i);
+ if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
+ if (isInTransientHide(wc)) {
+ mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ }
}
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ "transient-launch", mSyncId, activity);
}
- boolean isTransientHide(@NonNull Task task) {
- if (mTransientLaunches == null) return false;
- for (int i = 0; i < mTransientLaunches.size(); ++i) {
- if (mTransientLaunches.valueAt(i) == task) {
- return true;
- }
- }
- return false;
- }
-
/** @return whether `wc` is a descendent of a transient-hide window. */
boolean isInTransientHide(@NonNull WindowContainer wc) {
- if (mTransientLaunches == null) return false;
- for (int i = 0; i < mTransientLaunches.size(); ++i) {
- if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
+ if (mTransientHideTasks == null) return false;
+ for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTransientHideTasks.get(i);
+ if (wc == task || wc.isDescendantOf(task)) {
return true;
}
}
@@ -675,7 +698,7 @@
* needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
* Additionally, this gives shell the ability to better deal with merged transitions.
*/
- private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
+ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
final Point tmpPos = new Point();
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
@@ -728,8 +751,8 @@
} finally {
mController.mBuildingFinishLayers = false;
}
- if (rootLeash.isValid()) {
- t.reparent(rootLeash, null);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ t.reparent(info.getRoot(i).getLeash(), null);
}
}
@@ -814,6 +837,15 @@
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
}
+ mController.mFinishingTransition = this;
+
+ if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+ // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
+ // the update to make the activities in the tasks invisible-requested, then the next
+ // step can continue to commit the visibility.
+ mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
+ 0 /* configChanges */, true /* preserveWindows */);
+ }
boolean hasParticipatedDisplay = false;
boolean hasVisibleTransientLaunch = false;
@@ -980,6 +1012,7 @@
dc.removeImeSurfaceImmediately();
dc.handleCompleteDeferredRemoval();
}
+ validateVisibility();
mState = STATE_FINISHED;
mController.mTransitionTracer.logState(this);
@@ -995,6 +1028,7 @@
// Handle back animation if it's already started.
mController.mAtm.mBackNavigationController.handleDeferredBackAnimation(mTargets);
+ mController.mFinishingTransition = null;
}
void abort() {
@@ -1066,13 +1100,6 @@
Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
return;
}
- if (mTargetDisplays.isEmpty()) {
- mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
- }
- // While there can be multiple DC's involved. For now, we just use the first one as
- // the "primary" one for most things. Eventually, this will need to change, but, for the
- // time being, we don't have full cross-display transitions so it isn't a problem.
- final DisplayContent dc = mTargetDisplays.get(0);
// Commit the visibility of visible activities before calculateTransitionInfo(), so the
// TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
@@ -1082,6 +1109,9 @@
if (mState == STATE_ABORT) {
mController.abort(this);
+ // Fall-back to the default display if there isn't one participating.
+ final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
+ : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
@@ -1094,16 +1124,24 @@
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
mController.moveToPlaying(this);
- if (dc.isKeyguardLocked()) {
- mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
- }
-
// Check whether the participants were animated from back navigation.
final boolean markBackAnimated = mController.mAtm.mBackNavigationController
.containsBackAnimationTargets(this);
- // Resolve the animating targets from the participants
+ // Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
+
+ // Repopulate the displays based on the resolved targets.
+ mTargetDisplays.clear();
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
+ info.getRoot(i).getDisplayId());
+ mTargetDisplays.add(dc);
+ if (dc.isKeyguardLocked()) {
+ mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
+ }
+ }
+
if (markBackAnimated) {
mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
}
@@ -1125,9 +1163,12 @@
}
// TODO(b/188669821): Move to animation impl in shell.
- handleLegacyRecentsStartBehavior(dc, info);
+ for (int i = 0; i < mTargetDisplays.size(); ++i) {
+ handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
+ if (mRecentsDisplayId != INVALID_DISPLAY) break;
+ }
- handleNonAppWindowsInTransition(dc, mType, mFlags);
+ handleNonAppWindowsInTransition(mType, mFlags);
// The callback is only populated for custom activity-level client animations
sendRemoteCallback(mClientAnimationStartCallback);
@@ -1166,14 +1207,13 @@
// Record windowtokens (activity/wallpaper) that are expected to be visible after the
// transition animation. This will be used in finishTransition to prevent prematurely
- // committing visibility.
- for (int i = mParticipants.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mParticipants.valueAt(i);
- if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
- // don't include transient launches, though, since those are only temporarily visible.
- if (mTransientLaunches != null && wc.asActivityRecord() != null
- && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
- mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+ // committing visibility. Skip transient launches since those are only temporarily visible.
+ if (mTransientLaunches == null) {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mParticipants.valueAt(i);
+ if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
+ mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+ }
}
// Take task snapshots before the animation so that we can capture IME before it gets
@@ -1191,11 +1231,14 @@
// This is non-null only if display has changes. It handles the visible windows that don't
// need to be participated in the transition.
- final AsyncRotationController controller = dc.getAsyncRotationController();
- if (controller != null && containsChangeFor(dc, mTargets)) {
- controller.setupStartTransaction(transaction);
+ for (int i = 0; i < mTargetDisplays.size(); ++i) {
+ final DisplayContent dc = mTargetDisplays.get(i);
+ final AsyncRotationController controller = dc.getAsyncRotationController();
+ if (controller != null && containsChangeFor(dc, mTargets)) {
+ controller.setupStartTransaction(transaction);
+ }
}
- buildFinishTransaction(mFinishTransaction, info.getRootLeash());
+ buildFinishTransaction(mFinishTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
@@ -1216,10 +1259,13 @@
// client, we should finish and apply it here so the transactions aren't lost.
postCleanupOnFailure();
}
- final AccessibilityController accessibilityController =
- dc.mWmService.mAccessibilityController;
- if (accessibilityController.hasCallbacks()) {
- accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+ for (int i = 0; i < mTargetDisplays.size(); ++i) {
+ final DisplayContent dc = mTargetDisplays.get(i);
+ final AccessibilityController accessibilityController =
+ dc.mWmService.mAccessibilityController;
+ if (accessibilityController.hasCallbacks()) {
+ accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+ }
}
} else {
// No player registered or it's not enabled, so just finish/apply immediately
@@ -1261,7 +1307,7 @@
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(mToken);
+ mController.finishTransition(this);
}
private void cleanUpInternal() {
@@ -1296,16 +1342,15 @@
if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
return;
}
- mRecentsDisplayId = dc.mDisplayId;
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
+ ActivityRecord recentsActivity = null;
if (recentsAnimationInputConsumer != null) {
// find the top-most going-away activity and the recents activity. The top-most
// is used as layer reference while the recents is used for registering the consumer
// override.
- ActivityRecord recentsActivity = null;
ActivityRecord topActivity = null;
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -1329,6 +1374,12 @@
}
}
+ if (recentsActivity == null) {
+ // No recents activity on `dc`, its probably on a different display.
+ return;
+ }
+ mRecentsDisplayId = dc.mDisplayId;
+
// The rest of this function handles nav-bar reparenting
if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
@@ -1423,7 +1474,7 @@
}
}
- private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
+ private void handleNonAppWindowsInTransition(
@TransitionType int transit, @TransitionFlags int flags) {
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
// If the occlusion changed but the transition isn't an occlude/unocclude transition,
@@ -1801,6 +1852,41 @@
return wc instanceof DisplayContent;
}
+ private static int getDisplayId(@NonNull WindowContainer wc) {
+ return wc.getDisplayContent() != null
+ ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
+ }
+
+ @VisibleForTesting
+ static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
+ ArrayList<ChangeInfo> sortedTargets,
+ @NonNull SurfaceControl.Transaction startT) {
+ // There needs to be a root on each display.
+ for (int i = 0; i < sortedTargets.size(); ++i) {
+ final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
+ // Don't include wallpapers since they are in a different DA.
+ if (isWallpaper(wc)) continue;
+ final int endDisplayId = getDisplayId(wc);
+ if (endDisplayId < 0) continue;
+
+ // Check if Root was already created for this display with a higher-Z window
+ if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
+
+ WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
+
+ // Make leash based on highest (z-order) direct child of ancestor with a participant.
+ WindowContainer leashReference = wc;
+ while (leashReference.getParent() != ancestor) {
+ leashReference = leashReference.getParent();
+ }
+ final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
+ "Transition Root: " + leashReference.getName()).build();
+ startT.setLayer(rootLeash, leashReference.getLastLayer());
+ outInfo.addRootLeash(endDisplayId, rootLeash,
+ ancestor.getBounds().left, ancestor.getBounds().top);
+ }
+ }
+
/**
* Construct a TransitionInfo object from a set of targets and changes. Also populates the
* root surface.
@@ -1811,37 +1897,13 @@
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
ArrayList<ChangeInfo> sortedTargets,
- @Nullable SurfaceControl.Transaction startT) {
+ @NonNull SurfaceControl.Transaction startT) {
final TransitionInfo out = new TransitionInfo(type, flags);
-
- WindowContainer<?> topApp = null;
- for (int i = 0; i < sortedTargets.size(); i++) {
- final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
- if (!isWallpaper(wc)) {
- topApp = wc;
- break;
- }
- }
- if (topApp == null) {
- out.setRootLeash(new SurfaceControl(), 0, 0);
+ calculateTransitionRoots(out, sortedTargets, startT);
+ if (out.getRootCount() == 0) {
return out;
}
- WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
-
- // Make leash based on highest (z-order) direct child of ancestor with a participant.
- // TODO(b/261418859): Handle the case when the target contains window containers which
- // belong to a different display. As a workaround we use topApp, from which wallpaper
- // window container is removed, instead of sortedTargets here.
- WindowContainer leashReference = topApp;
- while (leashReference.getParent() != ancestor) {
- leashReference = leashReference.getParent();
- }
- final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
- "Transition Root: " + leashReference.getName()).build();
- startT.setLayer(rootLeash, leashReference.getLastLayer());
- out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
-
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
@@ -1859,8 +1921,10 @@
change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
}
change.setMode(info.getTransitMode(target));
+ info.mReadyMode = change.getMode();
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
+ change.setDisplayId(info.mDisplayId, getDisplayId(target));
final Task task = target.asTask();
final TaskFragment taskFragment = target.asTaskFragment();
@@ -1947,6 +2011,15 @@
}
TransitionInfo.AnimationOptions animOptions = null;
+
+ // Check if the top-most app is an activity (ie. activity->activity). If so, make sure to
+ // honor its custom transition options.
+ WindowContainer<?> topApp = null;
+ for (int i = 0; i < sortedTargets.size(); i++) {
+ if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
+ topApp = sortedTargets.get(i).mContainer;
+ break;
+ }
if (topApp.asActivityRecord() != null) {
final ActivityRecord topActivity = topApp.asActivityRecord();
animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
@@ -1997,14 +2070,15 @@
private static WindowContainer<?> findCommonAncestor(
@NonNull ArrayList<ChangeInfo> targets,
@NonNull WindowContainer<?> topApp) {
+ final int displayId = getDisplayId(topApp);
WindowContainer<?> ancestor = topApp.getParent();
// Go up ancestor parent chain until all targets are descendants. Ancestor should never be
// null because all targets are attached.
for (int i = targets.size() - 1; i >= 0; i--) {
final ChangeInfo change = targets.get(i);
final WindowContainer wc = change.mContainer;
- if (isWallpaper(wc)) {
- // Skip the non-app window.
+ if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
+ // Skip the non-app window or windows on a different display
continue;
}
while (!wc.isDescendantOf(ancestor)) {
@@ -2105,6 +2179,26 @@
return mainWin.getAttrs().rotationAnimation;
}
+ private void validateVisibility() {
+ for (int i = mTargets.size() - 1; i >= 0; --i) {
+ if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
+ return;
+ }
+ }
+ // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
+ // If the window container should be visible, then recover it.
+ mController.mStateValidators.add(() -> {
+ for (int i = mTargets.size() - 1; i >= 0; --i) {
+ final ChangeInfo change = mTargets.get(i);
+ if (!change.mContainer.isVisibleRequested()) continue;
+ Slog.e(TAG, "Force show for visible " + change.mContainer
+ + " which may be hidden by transition unexpectedly");
+ change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
+ change.mContainer.scheduleAnimation();
+ }
+ });
+ }
+
/** Applies the new configuration for the changed displays. */
void applyDisplayChangeIfNeeded() {
for (int i = mParticipants.size() - 1; i >= 0; --i) {
@@ -2180,6 +2274,7 @@
final Rect mAbsoluteBounds = new Rect();
boolean mShowWallpaper;
int mRotation = ROTATION_UNDEFINED;
+ int mDisplayId = -1;
@ActivityInfo.Config int mKnownConfigChanges;
/** These are just extra info. They aren't used for change-detection. */
@@ -2189,6 +2284,10 @@
SurfaceControl mSnapshot;
float mSnapshotLuma;
+ /** The mode which is set when the transition is ready. */
+ @TransitionInfo.TransitionMode
+ int mReadyMode;
+
ChangeInfo(@NonNull WindowContainer origState) {
mContainer = origState;
mVisible = origState.isVisibleRequested();
@@ -2197,6 +2296,7 @@
mShowWallpaper = origState.showWallpaper();
mRotation = origState.getWindowConfiguration().getRotation();
mStartParent = origState.getParent();
+ mDisplayId = getDisplayId(origState);
}
@VisibleForTesting
@@ -2228,7 +2328,8 @@
// assume no change in windowing-mode.
|| (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
|| !mContainer.getBounds().equals(mAbsoluteBounds)
- || mRotation != mContainer.getWindowConfiguration().getRotation();
+ || mRotation != mContainer.getWindowConfiguration().getRotation()
+ || mDisplayId != getDisplayId(mContainer);
}
@TransitionInfo.TransitionMode
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bacc6e6..86bb6b5 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -107,6 +107,9 @@
*/
private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
+ /** The currently finishing transition. */
+ Transition mFinishingTransition;
+
/**
* The windows that request to be invisible while it is in transition. After the transition
* is finished and the windows are no longer animating, their surfaces will be destroyed.
@@ -313,6 +316,11 @@
return false;
}
+ /** Returns {@code true} if the `wc` is a participant of the finishing transition. */
+ boolean inFinishingTransition(WindowContainer<?> wc) {
+ return mFinishingTransition != null && mFinishingTransition.mParticipants.contains(wc);
+ }
+
/** @return {@code true} if a transition is running */
boolean inTransition() {
// TODO(shell-transitions): eventually properly support multiple
@@ -358,11 +366,11 @@
}
boolean isTransientHide(@NonNull Task task) {
- if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
+ if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
return true;
}
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
+ if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
}
return false;
}
@@ -672,14 +680,13 @@
}
/** @see Transition#finishTransition */
- void finishTransition(@NonNull IBinder token) {
+ void finishTransition(Transition record) {
// It is usually a no-op but make sure that the metric consumer is removed.
- mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
+ mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
// It is a no-op if the transition did not change the display.
mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- final Transition record = Transition.fromBinder(token);
- if (record == null || !mPlayingTransitions.contains(record)) {
- Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+ if (!mPlayingTransitions.contains(record)) {
+ Slog.e(TAG, "Trying to finish a non-playing transition " + record);
return;
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 2b848d5..0b9ceea 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -163,14 +163,6 @@
if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
+ " mDrawState=" + w.mWinAnimator.mDrawState);
- if (w.mWillReplaceWindow && mWallpaperTarget == null
- && !mFindResults.useTopWallpaperAsTarget) {
- // When we are replacing a window and there was wallpaper before replacement, we want to
- // keep the window until the new windows fully appear and can determine the visibility,
- // to avoid flickering.
- mFindResults.setUseTopWallpaperAsTarget(true);
- }
-
final WindowContainer animatingContainer = w.mActivityRecord != null
? w.mActivityRecord.getAnimatingContainer() : null;
final boolean keyguardGoingAwayWithWallpaper = (animatingContainer != null
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c11391e..2596533 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -69,10 +69,6 @@
SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
private boolean mInitialized = false;
- // When set to true the animator will go over all windows after an animation frame is posted and
- // check if some got replaced and can be removed.
- private boolean mRemoveReplacedWindows = false;
-
private Choreographer mChoreographer;
/**
@@ -217,11 +213,6 @@
mService.closeSurfaceTransaction("WindowAnimator");
ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
- if (mRemoveReplacedWindows) {
- root.removeReplacedWindows();
- mRemoveReplacedWindows = false;
- }
-
mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
executeAfterPrepareSurfacesRunnables();
@@ -286,10 +277,6 @@
return displayAnimator;
}
- void requestRemovalOfReplacedWindows(WindowState win) {
- mRemoveReplacedWindows = true;
- }
-
void scheduleAnimation() {
if (!mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 2f3a70e..969afe5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -740,7 +740,7 @@
/**
* Show IME on imeTargetWindow once IME has finished layout.
*
- * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+ * @param imeTargetWindowToken token of the (IME target) window which IME should be shown.
* @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
public abstract void showImePostLayout(IBinder imeTargetWindowToken,
@@ -749,7 +749,7 @@
/**
* Hide IME using imeTargetWindow when requested.
*
- * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
+ * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME.
* @param displayId the id of the display the IME is on.
* @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 45cdacd..8582356 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,7 +88,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
@@ -186,7 +185,6 @@
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.configstore.V1_0.OptionalBool;
@@ -383,9 +381,6 @@
/** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
- /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
- static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
-
/** Amount of time to allow a last ANR message to exist before freeing the memory. */
static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
@@ -566,12 +561,6 @@
final WindowManagerGlobalLock mGlobalLock;
/**
- * List of app window tokens that are waiting for replacing windows. If the
- * replacement doesn't come in time the stale windows needs to be disposed of.
- */
- final ArrayList<ActivityRecord> mWindowReplacementTimeouts = new ArrayList<>();
-
- /**
* Windows that are being resized. Used so we can tell the client about
* the resize after closing the transaction in which we resized the
* underlying surface.
@@ -1775,15 +1764,6 @@
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
- // Check if we need to prepare a transition for replacing window first.
- if (!win.mTransitionController.isShellTransitionsEnabled()
- && activity != null && activity.isVisible()
- && !prepareWindowReplacementTransition(activity)) {
- // If not, check if need to set up a dummy transition during display freeze
- // so that the unfreeze wait for the apps to draw. This might be needed if
- // the app is relaunching.
- prepareNoneTransitionForRelaunching(activity);
- }
if (displayPolicy.areSystemBarsForcedConsumedLw()) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
@@ -1945,48 +1925,6 @@
}
/**
- * Returns true if we're done setting up any transitions.
- */
- private boolean prepareWindowReplacementTransition(ActivityRecord activity) {
- activity.clearAllDrawn();
- final WindowState replacedWindow = activity.getReplacingWindow();
- if (replacedWindow == null) {
- // We expect to already receive a request to remove the old window. If it did not
- // happen, let's just simply add a window.
- return false;
- }
- // We use the visible frame, because we want the animation to morph the window from what
- // was visible to the user to the final destination of the new window.
- final Rect frame = new Rect(replacedWindow.getFrame());
- final WindowManager.LayoutParams attrs = replacedWindow.mAttrs;
- frame.inset(replacedWindow.getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
- frame, attrs.type, replacedWindow.getWindowingMode(), attrs.softInputMode,
- attrs.flags));
- // We treat this as if this activity was opening, so we can trigger the app transition
- // animation and piggy-back on existing transition animation infrastructure.
- final DisplayContent dc = activity.getDisplayContent();
- dc.mOpeningApps.add(activity);
- dc.prepareAppTransition(TRANSIT_RELAUNCH);
- dc.mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
- frame.width(), frame.height());
- dc.executeAppTransition();
- return true;
- }
-
- private void prepareNoneTransitionForRelaunching(ActivityRecord activity) {
- // Set up a none-transition and add the app to opening apps, so that the display
- // unfreeze wait for the apps to be drawn.
- // Note that if the display unfroze already because app unfreeze timed out,
- // we don't set up the transition anymore and just let it go.
- final DisplayContent dc = activity.getDisplayContent();
- if (mDisplayFrozen && !dc.mOpeningApps.contains(activity) && activity.isRelaunching()) {
- dc.mOpeningApps.add(activity);
- dc.prepareAppTransition(TRANSIT_NONE);
- dc.executeAppTransition();
- }
- }
-
- /**
* Set whether screen capture is disabled for all windows of a specific user from
* the device policy cache.
*/
@@ -2391,12 +2329,6 @@
// If we are not currently running the exit animation, we need to see about starting
// one.
- // We don't want to animate visibility of windows which are pending replacement.
- // In the case of activity relaunch child windows could request visibility changes as
- // they are detached from the main application window during the tear down process.
- // If we satisfied these visibility changes though, we would cause a visual glitch
- // hiding the window before it's replacement was available. So we just do nothing on
- // our side.
// This must be called before the call to performSurfacePlacement.
if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) {
if (DEBUG_VISIBILITY) {
@@ -2404,20 +2336,18 @@
"Relayout invis " + win + ": mAnimatingExit=" + win.mAnimatingExit);
}
result |= RELAYOUT_RES_SURFACE_CHANGED;
- if (!win.mWillReplaceWindow) {
- // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
- // in DC#pendingLayoutChanges and update the wallpaper target later.
- // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
- // when the window is about to exit, so we update the wallpaper target
- // immediately here. Otherwise this window will be stuck in exiting and its
- // surface remains on the screen.
- // TODO(b/189856716): Allow destroying surface even if it belongs to the
- // keyguard target.
- if (wallpaperMayMove) {
- displayContent.mWallpaperController.adjustWallpaperWindows();
- }
- tryStartExitingAnimation(win, winAnimator);
+ // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
+ // in DC#pendingLayoutChanges and update the wallpaper target later.
+ // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
+ // when the window is about to exit, so we update the wallpaper target
+ // immediately here. Otherwise this window will be stuck in exiting and its
+ // surface remains on the screen.
+ // TODO(b/189856716): Allow destroying surface even if it belongs to the
+ // keyguard target.
+ if (wallpaperMayMove) {
+ displayContent.mWallpaperController.adjustWallpaperWindows();
}
+ tryStartExitingAnimation(win, winAnimator);
}
// Create surfaceControl before surface placement otherwise layout will be skipped
@@ -5329,8 +5259,6 @@
public static final int UPDATE_MULTI_WINDOW_STACKS = 41;
- public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
-
public static final int UPDATE_ANIMATION_SCALE = 51;
public static final int WINDOW_HIDE_TIMEOUT = 52;
public static final int RESTORE_POINTER_ICON = 55;
@@ -5556,16 +5484,6 @@
}
break;
}
- case WINDOW_REPLACEMENT_TIMEOUT: {
- synchronized (mGlobalLock) {
- for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = mWindowReplacementTimeouts.get(i);
- activity.onWindowReplacementTimeout();
- }
- mWindowReplacementTimeouts.clear();
- }
- break;
- }
case WINDOW_HIDE_TIMEOUT: {
final WindowState window = (WindowState) msg.obj;
synchronized (mGlobalLock) {
@@ -7084,98 +7002,6 @@
return mGlobalLock;
}
- /**
- * Hint to a token that its activity will relaunch, which will trigger removal and addition of
- * a window.
- *
- * @param token Application token for which the activity will be relaunched.
- */
- void setWillReplaceWindow(IBinder token, boolean animate) {
- final ActivityRecord activity = mRoot.getActivityRecord(token);
- if (activity == null) {
- ProtoLog.w(WM_ERROR, "Attempted to set replacing window on non-existing app token %s",
- token);
- return;
- }
- if (!activity.hasContentToDisplay()) {
- ProtoLog.w(WM_ERROR,
- "Attempted to set replacing window on app token with no content %s",
- token);
- return;
- }
- activity.setWillReplaceWindows(animate);
- }
-
- /**
- * Hint to a token that its windows will be replaced across activity relaunch.
- * The windows would otherwise be removed shortly following this as the
- * activity is torn down.
- * @param token Application token for which the activity will be relaunched.
- * @param childrenOnly Whether to mark only child windows for replacement
- * (for the case where main windows are being preserved/
- * reused rather than replaced).
- *
- */
- // TODO: The s at the end of the method name is the only difference with the name of the method
- // above. We should combine them or find better names.
- void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
- synchronized (mGlobalLock) {
- final ActivityRecord activity = mRoot.getActivityRecord(token);
- if (activity == null) {
- ProtoLog.w(WM_ERROR,
- "Attempted to set replacing window on non-existing app token %s",
- token);
- return;
- }
- if (!activity.hasContentToDisplay()) {
- ProtoLog.w(WM_ERROR,
- "Attempted to set replacing window on app token with no content %s",
- token);
- return;
- }
-
- if (childrenOnly) {
- activity.setWillReplaceChildWindows();
- } else {
- activity.setWillReplaceWindows(false /* animate */);
- }
-
- scheduleClearWillReplaceWindows(token, true /* replacing */);
- }
- }
-
- /**
- * If we're replacing the window, schedule a timer to clear the replaced window
- * after a timeout, in case the replacing window is not coming.
- *
- * If we're not replacing the window, clear the replace window settings of the app.
- *
- * @param token Application token for the activity whose window might be replaced.
- * @param replacing Whether the window is being replaced or not.
- */
- void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
- final ActivityRecord activity = mRoot.getActivityRecord(token);
- if (activity == null) {
- ProtoLog.w(WM_ERROR, "Attempted to reset replacing window on non-existing app token %s",
- token);
- return;
- }
- if (replacing) {
- scheduleWindowReplacementTimeouts(activity);
- } else {
- activity.clearWillReplaceWindows();
- }
- }
-
- void scheduleWindowReplacementTimeouts(ActivityRecord activity) {
- if (!mWindowReplacementTimeouts.contains(activity)) {
- mWindowReplacementTimeouts.add(activity);
- }
- mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
- mH.sendEmptyMessageDelayed(
- H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
- }
-
@Override
public int getDockedStackSide() {
return 0;
@@ -7361,14 +7187,6 @@
.setPointerIconType(PointerIcon.TYPE_DEFAULT);
}
}
-
- PointF getLatestMousePosition() {
- synchronized (mMousePositionTracker) {
- return new PointF(mMousePositionTracker.mLatestMouseX,
- mMousePositionTracker.mLatestMouseY);
- }
- }
-
void setMousePointerDisplayId(int displayId) {
mMousePositionTracker.setPointerDisplayId(displayId);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c3c87af..17d4f1b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -135,7 +135,7 @@
*/
static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE
- | ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_DENSITY;
static final int CONTROLLABLE_WINDOW_CONFIGS = WINDOW_CONFIG_BOUNDS
| WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
@@ -391,9 +391,14 @@
// apply the incoming transaction before finish in case it alters the visibility
// of the participants.
if (t != null) {
+ // Set the finishing transition before applyTransaction so the visibility
+ // changes of the transition participants will only set visible-requested
+ // and still let finishTransition handle the participants.
+ mTransitionController.mFinishingTransition = transition;
applyTransaction(t, syncId, null /*transition*/, caller, transition);
}
- getTransitionController().finishTransition(transitionToken);
+ mTransitionController.finishTransition(transition);
+ mTransitionController.mFinishingTransition = null;
if (syncId >= 0) {
setSyncReady(syncId);
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 694f1be..834b708 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1381,6 +1381,13 @@
}
/**
+ * Destroys the WindwoProcessController, after the process has been removed.
+ */
+ void destroy() {
+ unregisterConfigurationListeners();
+ }
+
+ /**
* Check if activity configuration override for the activity process needs an update and perform
* if needed. By default we try to override the process configuration to match the top activity
* config to increase app compatibility with multi-window and multi-display. The process will
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 2767972..424b043 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -19,8 +19,8 @@
import android.util.ArraySet;
import android.util.SparseArray;
-import java.util.Map;
import java.util.HashMap;
+import java.util.Map;
final class WindowProcessControllerMap {
@@ -67,6 +67,7 @@
mPidMap.remove(pid);
// remove process from mUidMap
removeProcessFromUidMap(proc);
+ proc.destroy();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f86b997..8a083aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -59,13 +59,11 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -221,8 +219,6 @@
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -572,12 +568,6 @@
boolean mRemoveOnExit;
/**
- * Whether the app died while it was visible, if true we might need
- * to continue to show it until it's restarted.
- */
- boolean mAppDied;
-
- /**
* Set when the orientation is changing and this window has not yet
* been updated for the new orientation.
*/
@@ -640,22 +630,6 @@
boolean mHasSurface = false;
- // This window will be replaced due to relaunch. This allows window manager
- // to differentiate between simple removal of a window and replacement. In the latter case it
- // will preserve the old window until the new one is drawn.
- boolean mWillReplaceWindow = false;
- // If true, the replaced window was already requested to be removed.
- private boolean mReplacingRemoveRequested = false;
- // Whether the replacement of the window should trigger app transition animation.
- private boolean mAnimateReplacingWindow = false;
- // If not null, the window that will be used to replace the old one. This is being set when
- // the window is added and unset when this window reports its first draw.
- private WindowState mReplacementWindow = null;
- // For the new window in the replacement transition, if we have
- // requested to replace without animation, then we should
- // make sure we also don't apply an enter animation for
- // the new window.
- boolean mSkipEnterAnimationForSeamlessReplacement = false;
// Whether this window is being moved via the resize API
private boolean mMovedByResize;
@@ -760,7 +734,6 @@
*/
private InsetsState mFrozenInsetsState;
- private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
/**
@@ -1318,13 +1291,6 @@
}
boolean skipLayout() {
- if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
- // This window is being replaced and either already got information that it's being
- // removed or we are still waiting for some information. Because of this we don't
- // want to apply any more changes to it, so it remains in this state until new window
- // appears.
- return true;
- }
// Skip layout of the window when in transition to pip mode.
return mActivityRecord != null && mActivityRecord.mWaitForEnteringPinnedMode;
}
@@ -1504,13 +1470,6 @@
}
}
- // If it's a dead window left on screen, and the configuration changed, there is nothing
- // we can do about it. Remove the window now.
- if (mActivityRecord != null && mAppDied) {
- mActivityRecord.removeDeadWindows();
- return;
- }
-
onResizeHandled();
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
@@ -2009,7 +1968,7 @@
boolean isInteresting() {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
- return mActivityRecord != null && !mAppDied
+ return mActivityRecord != null
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE
&& (recentsAnimationController == null
@@ -2370,24 +2329,6 @@
}
}
- void onWindowReplacementTimeout() {
- if (mWillReplaceWindow) {
- // Since the window already timed out, remove it immediately now.
- // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as
- // the latter delays removal on certain conditions, which will leave the stale window
- // in the root task and marked mWillReplaceWindow=false, so the window will never be
- // removed.
- //
- // Also removes child windows.
- removeImmediately();
- } else {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.onWindowReplacementTimeout();
- }
- }
- }
-
@Override
void removeImmediately() {
if (mRemoved) {
@@ -2404,11 +2345,6 @@
mWinAnimator.destroySurfaceLocked(getSyncTransaction());
super.removeImmediately();
- mWillReplaceWindow = false;
- if (mReplacementWindow != null) {
- mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
- }
-
final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
// Remove the attached IME screenshot surface.
@@ -2448,11 +2384,6 @@
@Override
void removeIfPossible() {
- super.removeIfPossible();
- removeIfPossible(false /*keepVisibleDeadWindow*/);
- }
-
- private void removeIfPossible(boolean keepVisibleDeadWindow) {
mWindowRemovalAllowed = true;
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
@@ -2493,12 +2424,11 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
- + "mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
+ + "mDisplayFrozen=%b callers=%s",
this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
- mWillReplaceWindow,
mWmService.mDisplayFrozen, Debug.getCallers(6));
// Visibility of the removed window. Will be used later to update orientation later on.
@@ -2508,40 +2438,9 @@
// window until the animation is done. If the display is frozen, just remove immediately,
// since the animation wouldn't be seen.
if (mHasSurface && mToken.okToAnimate()) {
- if (mWillReplaceWindow) {
- // This window is going to be replaced. We need to keep it around until the new one
- // gets added, then we will get rid of this one.
- ProtoLog.v(WM_DEBUG_ADD_REMOVE,
- "Preserving %s until the new one is added", this);
- // TODO: We are overloading mAnimatingExit flag to prevent the window state from
- // been removed. We probably need another flag to indicate that window removal
- // should be deffered vs. overloading the flag that says we are playing an exit
- // animation.
- ProtoLog.v(WM_DEBUG_ANIM,
- "Set animatingExit: reason=remove/replaceWindow win=%s", this);
- mAnimatingExit = true;
- mReplacingRemoveRequested = true;
- return;
- }
-
// If we are not currently running the exit animation, we need to see about starting one
wasVisible = isVisible();
- if (keepVisibleDeadWindow) {
- ProtoLog.v(WM_DEBUG_ADD_REMOVE,
- "Not removing %s because app died while it's visible", this);
-
- mAppDied = true;
- setDisplayLayoutNeeded();
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
-
- // Set up a replacement input channel since the app is now dead.
- // We need to catch tapping on the dead window to restart the app.
- openInputChannel(null);
- displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
- return;
- }
-
// Remove immediately if there is display transition because the animation is
// usually unnoticeable (e.g. covered by rotation animation) and the animation
// bounds could be inconsistent, such as depending on when the window applies
@@ -2715,19 +2614,7 @@
|| (isVisible() && mActivityRecord != null && mActivityRecord.isVisible());
}
- private final class DeadWindowEventReceiver extends InputEventReceiver {
- DeadWindowEventReceiver(InputChannel inputChannel) {
- super(inputChannel, mWmService.mH.getLooper());
- }
- @Override
- public void onInputEvent(InputEvent event) {
- finishInputEvent(event, true);
- }
- }
- /** Fake event receiver for windows that died visible. */
- private DeadWindowEventReceiver mDeadWindowEventReceiver;
-
- void openInputChannel(InputChannel outInputChannel) {
+ void openInputChannel(@NonNull InputChannel outInputChannel) {
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
@@ -2736,14 +2623,7 @@
mInputChannelToken = mInputChannel.getToken();
mInputWindowHandle.setToken(mInputChannelToken);
mWmService.mInputToWindowMap.put(mInputChannelToken, this);
- if (outInputChannel != null) {
- mInputChannel.copyTo(outInputChannel);
- } else {
- // If the window died visible, we setup a fake input channel, so that taps
- // can still detected by input monitor channel, and we can relaunch the app.
- // Create fake event receiver that simply reports all events as handled.
- mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
- }
+ mInputChannel.copyTo(outInputChannel);
}
/**
@@ -2754,10 +2634,6 @@
}
void disposeInputChannel() {
- if (mDeadWindowEventReceiver != null) {
- mDeadWindowEventReceiver.dispose();
- mDeadWindowEventReceiver = null;
- }
if (mInputChannelToken != null) {
// Unregister server channel first otherwise it complains about broken channel.
mWmService.mInputManager.removeInputChannel(mInputChannelToken);
@@ -2773,53 +2649,6 @@
mInputWindowHandle.setToken(null);
}
- /** Returns true if the replacement window was removed. */
- boolean removeReplacedWindowIfNeeded(WindowState replacement) {
- if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) {
- replacement.mSkipEnterAnimationForSeamlessReplacement = false;
- removeReplacedWindow();
- return true;
- }
-
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- if (c.removeReplacedWindowIfNeeded(replacement)) {
- return true;
- }
- }
- return false;
- }
-
- private void removeReplacedWindow() {
- ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Removing replaced window: %s", this);
- mWillReplaceWindow = false;
- mAnimateReplacingWindow = false;
- mReplacingRemoveRequested = false;
- mReplacementWindow = null;
- if (mAnimatingExit || !mAnimateReplacingWindow) {
- removeImmediately();
- }
- }
-
- boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) {
- boolean replacementSet = false;
-
- if (mWillReplaceWindow && mReplacementWindow == null
- && getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) {
-
- mReplacementWindow = replacementCandidate;
- replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow;
- replacementSet = true;
- }
-
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
- }
-
- return replacementSet;
- }
-
void setDisplayLayoutNeeded() {
final DisplayContent dc = getDisplayContent();
if (dc != null) {
@@ -3084,11 +2913,10 @@
.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
- final DisplayContent dc = getDisplayContent();
if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) {
mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
}
- win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
+ win.removeIfPossible();
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
WindowState.this.removeIfPossible();
@@ -3100,32 +2928,6 @@
}
}
- /**
- * Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
- * because we want to preserve its location on screen to be re-activated later when the user
- * interacts with it.
- */
- private boolean shouldKeepVisibleDeadAppWindow() {
- if (!isVisible() || mActivityRecord == null || !mActivityRecord.isClientVisible()) {
- // Not a visible app window or the app isn't dead.
- return false;
- }
-
- if (mAttrs.token != mClient.asBinder()) {
- // The window was add by a client using another client's app token. We don't want to
- // keep the dead window around for this case since this is meant for 'real' apps.
- return false;
- }
-
- if (mAttrs.type == TYPE_APPLICATION_STARTING) {
- // We don't keep starting windows since they were added by the window manager before
- // the app even launched.
- return false;
- }
-
- return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
- }
-
/** Returns {@code true} if this window desires key events. */
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
@@ -3972,7 +3774,7 @@
@Override
public void notifyInsetsControlChanged() {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
- if (mAppDied || mRemoved) {
+ if (mRemoved) {
return;
}
final InsetsStateController stateController =
@@ -4278,7 +4080,6 @@
pw.println(prefix + "mToken=" + mToken);
if (mActivityRecord != null) {
pw.println(prefix + "mActivityRecord=" + mActivityRecord);
- pw.print(prefix + "mAppDied=" + mAppDied);
pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated());
pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn());
}
@@ -4478,49 +4279,6 @@
return parent != null && parent.isGoneForLayout();
}
- void setWillReplaceWindow(boolean animate) {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- c.setWillReplaceWindow(animate);
- }
-
- if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
- || mAttrs.type == TYPE_APPLICATION_STARTING) {
- // We don't set replacing on starting windows since they are added by window manager and
- // not the client so won't be replaced by the client.
- return;
- }
-
- mWillReplaceWindow = true;
- mReplacementWindow = null;
- mAnimateReplacingWindow = animate;
- }
-
- void clearWillReplaceWindow() {
- mWillReplaceWindow = false;
- mReplacementWindow = null;
- mAnimateReplacingWindow = false;
-
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- c.clearWillReplaceWindow();
- }
- }
-
- boolean waitingForReplacement() {
- if (mWillReplaceWindow) {
- return true;
- }
-
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- if (c.waitingForReplacement()) {
- return true;
- }
- }
- return false;
- }
-
void requestUpdateWallpaperIfNeeded() {
final DisplayContent dc = getDisplayContent();
if (dc != null && ((mIsWallpaper && !mLastConfigReportedToClient) || hasWallpaper())) {
@@ -4551,43 +4309,6 @@
return winY;
}
- // During activity relaunch due to resize, we sometimes use window replacement
- // for only child windows (as the main window is handled by window preservation)
- // and the big surface.
- //
- // Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to
- // TYPE_BASE_APPLICATION) are not children in the sense of an attached window,
- // we also want to replace them at such phases, as they won't be covered by window
- // preservation, and in general we expect them to return following relaunch.
- boolean shouldBeReplacedWithChildren() {
- return mIsChildWindow || mAttrs.type == TYPE_APPLICATION
- || mAttrs.type == TYPE_DRAWN_APPLICATION;
- }
-
- void setWillReplaceChildWindows() {
- if (shouldBeReplacedWithChildren()) {
- setWillReplaceWindow(false /* animate */);
- }
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- c.setWillReplaceChildWindows();
- }
- }
-
- WindowState getReplacingWindow() {
- if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) {
- return this;
- }
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- final WindowState replacing = c.getReplacingWindow();
- if (replacing != null) {
- return replacing;
- }
- }
- return null;
- }
-
int getRotationAnimationHint() {
if (mActivityRecord != null) {
return mActivityRecord.mRotationAnimationHint;
@@ -5077,14 +4798,11 @@
boolean clearAnimatingFlags() {
boolean didSomething = false;
- // We don't want to clear it out for windows that get replaced, because the
- // animation depends on the flag to remove the replaced window.
- //
// We also don't clear the mAnimatingExit flag for windows which have the
// mRemoveOnExit flag. This indicates an explicit remove request has been issued
// by the client. We should let animation proceed and not clear this flag or
// they won't eventually be removed by WindowStateAnimator#finishExit.
- if (!mWillReplaceWindow && !mRemoveOnExit) {
+ if (!mRemoveOnExit) {
// Clear mAnimating flag together with mAnimatingExit. When animation
// changes from exiting to entering, we need to clear this flag until the
// new animation gets applied, so that isAnimationStarting() becomes true
@@ -5399,7 +5117,7 @@
return activity.needsZBoost();
}
}
- return mWillReplaceWindow;
+ return false;
}
private boolean isStartingWindowAssociatedToTask() {
@@ -5407,10 +5125,7 @@
}
private void applyDims() {
- if (!mAnimatingExit && mAppDied) {
- mIsDimming = true;
- getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
- } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
+ if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
&& isVisibleNow() && !mHidden) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e8625bc..3aac816 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -449,7 +449,6 @@
if (prepared && mDrawState == HAS_DRAWN) {
if (mLastHidden) {
mSurfaceController.showRobustly(t);
- mAnimator.requestRemovalOfReplacedWindows(w);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -504,13 +503,6 @@
}
void applyEnterAnimationLocked() {
- // If we are the new part of a window replacement transition and we have requested
- // not to animate, we instead want to make it seamless, so we don't want to apply
- // an enter transition.
- if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
- return;
- }
-
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index c6b7898..327483e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -315,17 +315,6 @@
return mChildren.isEmpty();
}
- WindowState getReplacingWindow() {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = mChildren.get(i);
- final WindowState replacing = win.getReplacingWindow();
- if (replacing != null) {
- return replacing;
- }
- }
- return null;
- }
-
/** Return true if this token has a window that wants the wallpaper displayed behind it. */
boolean windowsCanBeWallpaperTarget() {
for (int j = mChildren.size() - 1; j >= 0; j--) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index b4e2fb6..da44da4 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -308,6 +308,7 @@
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
+ FloatPoint getMouseCursorPosition();
/* --- InputReaderPolicyInterface implementation --- */
@@ -366,7 +367,7 @@
virtual PointerIconStyle getDefaultPointerIconId();
virtual PointerIconStyle getDefaultStylusIconId();
virtual PointerIconStyle getCustomPointerIconId();
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
/* --- If touch mode is enabled per display or global --- */
@@ -730,11 +731,11 @@
return controller;
}
-void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
- float yPos) {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId,
+ const FloatPoint& position) {
JNIEnv* env = jniEnv();
env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
- xPos, yPos);
+ position.x, position.y);
checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
@@ -1655,6 +1656,14 @@
return static_cast<bool>(enabled);
}
+FloatPoint NativeInputManager::getMouseCursorPosition() {
+ AutoMutex _l(mLock);
+ const auto pc = mLocked.pointerController.lock();
+ if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
+
+ return pc->getPosition();
+}
+
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2547,6 +2556,15 @@
im->setStylusButtonMotionEventsEnabled(enabled);
}
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const auto p = im->getMouseCursorPosition();
+ const std::array<float, 2> arr = {{p.x, p.y}};
+ jfloatArray outArr = env->NewFloatArray(2);
+ env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
+ return outArr;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2640,6 +2658,7 @@
{"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
{"setStylusButtonMotionEventsEnabled", "(Z)V",
(void*)nativeSetStylusButtonMotionEventsEnabled},
+ {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
};
#define FIND_CLASS(var, className) \
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 94260e20..e09c0a2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -122,7 +122,7 @@
private void respondToClientWithResponseAndFinish() {
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
ApiStatus.CLIENT_CANCELED);
@@ -134,7 +134,7 @@
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
ApiStatus.SUCCESS);
} catch (RemoteException e) {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
Log.i(TAG, "Issue while propagating the response to the client");
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 47b8c7d..4f8235a 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -83,6 +83,7 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
@@ -90,6 +91,7 @@
mClientAppInfo.getPackageName()),
providerDataList));
} catch (RemoteException e) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(false);
respondToClientWithErrorAndFinish(
CreateCredentialException.TYPE_UNKNOWN,
"Unable to invoke selector");
@@ -99,14 +101,16 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable CreateCredentialResponse response) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(true);
+ mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
setChosenMetric(componentName);
if (response != null) {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
@@ -138,6 +142,8 @@
private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
Log.i(TAG, "respondToClientWithResponseAndFinish");
+ // TODO immediately add exception bit to chosen provider and do final emits across all
+ // including sequenceCounter!
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
Log.i(TAG, "Request has already been completed. This is strange.");
return;
@@ -162,6 +168,7 @@
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
Log.i(TAG, "respondToClientWithErrorAndFinish");
+ // TODO add exception bit
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
Log.i(TAG, "Request has already been completed. This is strange.");
return;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 10d3dc0..85a48d9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -41,6 +41,7 @@
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -438,6 +439,17 @@
return cancelTransport;
}
+ @Override
+ public ICancellationSignal executeGetPendingCredential(
+ GetCredentialRequest request,
+ IGetPendingCredentialCallback callback,
+ final String callingPackage) {
+ // TODO(b/273308895): implement
+
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ return cancelTransport;
+ }
+
private void processGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
@@ -580,9 +592,13 @@
}
private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
- var initMetric = session.mInitialPhaseMetric;
- initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
- MetricUtilities.logApiCalled(initMetric);
+ try {
+ var initMetric = session.mInitialPhaseMetric;
+ initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
+ MetricUtilities.logApiCalled(initMetric);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8e90c09..00fbbba 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -82,12 +82,14 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
providerDataList));
} catch (RemoteException e) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(false);
respondToClientWithErrorAndFinish(
GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
}
@@ -96,14 +98,16 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(true);
+ mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
setChosenMetric(componentName);
if (response != null) {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
- mChosenProviderMetric.setChosenProviderStatus(
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 1b3e37a..99f3b3e 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -25,7 +25,7 @@
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
import com.android.server.credentials.metrics.InitialPhaseMetric;
import java.util.Map;
@@ -81,19 +81,13 @@
}
/**
- * The most common logging helper, handles the overall status of the API request with the
- * provider status and latencies. Other versions of this method may be more useful depending
- * on the situation, as this is geared towards the logging of {@link ProviderSession} types.
+ * A logging utility used primarily for the candidate phase of the current metric setup.
*
- * @param apiName the api type to log
- * @param apiStatus the api status to log
* @param providers a map with known providers
- * @param callingUid the calling UID of the client app
- * @param chosenProviderMetric the metric data type of the final chosen provider
+ * @param emitSequenceId an emitted sequence id for the current session
*/
- protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
- Map<String, ProviderSession> providers, int callingUid,
- ChosenProviderMetric chosenProviderMetric) {
+ protected static void logApiCalled(Map<String, ProviderSession> providers,
+ int emitSequenceId) {
try {
var providerSessions = providers.values();
int providerSize = providerSessions.size();
@@ -102,7 +96,42 @@
int[] candidateStatusList = new int[providerSize];
int index = 0;
for (var session : providerSessions) {
- CandidatePhaseMetric metric = session.mCandidateProviderMetric;
+ CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
+ candidateUidList[index] = metric.getCandidateUid();
+ candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
+ candidateStatusList[index] = metric.getProviderQueryStatus();
+ index++;
+ }
+ // TODO Handle the emit here
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ /**
+ * The most common logging helper, handles the overall status of the API request with the
+ * provider status and latencies. Other versions of this method may be more useful depending
+ * on the situation, as this is geared towards the logging of {@link ProviderSession} types.
+ *
+ * @param apiName the api type to log
+ * @param apiStatus the api status to log
+ * @param providers a map with known providers
+ * @param callingUid the calling UID of the client app
+ * @param chosenProviderFinalPhaseMetric the metric data type of the final chosen provider
+ * TODO remove soon
+ */
+ protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
+ Map<String, ProviderSession> providers, int callingUid,
+ ChosenProviderFinalPhaseMetric chosenProviderFinalPhaseMetric) {
+ try {
+ var providerSessions = providers.values();
+ int providerSize = providerSessions.size();
+ int[] candidateUidList = new int[providerSize];
+ int[] candidateQueryRoundTripTimeList = new int[providerSize];
+ int[] candidateStatusList = new int[providerSize];
+ int index = 0;
+ for (var session : providerSessions) {
+ CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
candidateUidList[index] = metric.getCandidateUid();
candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
candidateStatusList[index] = metric.getProviderQueryStatus();
@@ -116,12 +145,13 @@
/* repeated_candidate_provider_round_trip_time_query_microseconds */
candidateQueryRoundTripTimeList,
/* repeated_candidate_provider_status */ candidateStatusList,
- /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
+ /* chosen_provider_uid */ chosenProviderFinalPhaseMetric.getChosenUid(),
/* chosen_provider_round_trip_time_overall_microseconds */
- chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+ chosenProviderFinalPhaseMetric.getEntireProviderLatencyMicroseconds(),
/* chosen_provider_final_phase_microseconds (backwards compat only) */
DEFAULT_INT_32,
- /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
+ /* chosen_provider_status */ chosenProviderFinalPhaseMetric
+ .getChosenProviderStatus());
} catch (Exception e) {
Log.w(TAG, "Unexpected error during metric logging: " + e);
}
@@ -132,6 +162,7 @@
* contain default values for all other optional parameters.
*
* TODO(b/271135048) - given space requirements, this may be a good candidate for another atom
+ * TODO immediately remove and carry over TODO to new log for this setup
*
* @param apiName the api name to log
* @param apiStatus the status to log
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index ab29acc..b86daba 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -33,7 +33,7 @@
*
* @hide
*/
-public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
Void>
implements
RemoteCredentialService.ProviderCallbacks<Void> {
@@ -42,7 +42,8 @@
private ClearCredentialStateException mProviderException;
/** Creates a new provider session to be used by the request session. */
- @Nullable public static ProviderClearSession createNewSession(
+ @Nullable
+ public static ProviderClearSession createNewSession(
Context context,
@UserIdInt int userId,
CredentialProviderInfo providerInfo,
@@ -53,7 +54,7 @@
clearRequestSession.mClientRequest,
clearRequestSession.mClientAppInfo);
return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
- remoteCredentialService, providerRequest);
+ remoteCredentialService, providerRequest);
}
@Nullable
@@ -90,6 +91,7 @@
if (exception instanceof ClearCredentialStateException) {
mProviderException = (ClearCredentialStateException) exception;
}
+ captureCandidateFailure();
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -120,7 +122,7 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ startCandidateMetrics();
mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8c9c6cf..bbbb156 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -40,6 +40,8 @@
import android.util.Pair;
import android.util.Slog;
+import com.android.server.credentials.metrics.EntryEnum;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -65,7 +67,8 @@
private final ProviderResponseDataHandler mProviderResponseDataHandler;
/** Creates a new provider session to be used by the request session. */
- @Nullable public static ProviderCreateSession createNewSession(
+ @Nullable
+ public static ProviderCreateSession createNewSession(
Context context,
@UserIdInt int userId,
CredentialProviderInfo providerInfo,
@@ -155,6 +158,7 @@
// Store query phase exception for aggregation with final response
mProviderException = (CreateCredentialException) exception;
}
+ captureCandidateFailure();
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -175,14 +179,32 @@
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
+ gatheCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
+ gatheCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
}
}
+ private void gatheCandidateEntryMetrics(BeginCreateCredentialResponse response) {
+ try {
+ var createEntries = response.getCreateEntries();
+ int numCreateEntries = createEntries == null ? 0 : createEntries.size();
+ // TODO confirm how to get types from slice
+ if (numCreateEntries > 0) {
+ createEntries.forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+ }
+ mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
@Override
- @Nullable protected CreateCredentialProviderData prepareUiData()
+ @Nullable
+ protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
@@ -226,7 +248,7 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ startCandidateMetrics();
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
}
}
@@ -298,12 +320,14 @@
}
private class ProviderResponseDataHandler {
- @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+ @Nullable
+ private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
- @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+ @Nullable
+ private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -316,6 +340,7 @@
setRemoteEntry(remoteEntry);
}
}
+
public void addCreateEntry(CreateEntry createEntry) {
String id = generateUniqueId();
Entry entry = new Entry(SAVE_ENTRY_KEY,
@@ -366,6 +391,7 @@
private boolean isEmptyResponse() {
return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null;
}
+
@Nullable
public RemoteEntry getRemoteEntry(String entryKey) {
return mUiRemoteEntry == null || mUiRemoteEntry
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 955b721..bf1db37 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -43,6 +43,8 @@
import android.util.Pair;
import android.util.Slog;
+import com.android.server.credentials.metrics.EntryEnum;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -61,13 +63,13 @@
RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
private static final String TAG = "ProviderGetSession";
// Key to be used as the entry key for an action entry
- private static final String ACTION_ENTRY_KEY = "action_key";
+ public static final String ACTION_ENTRY_KEY = "action_key";
// Key to be used as the entry key for the authentication entry
- private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
+ public static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
// Key to be used as an entry key for a remote entry
- private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
+ public static final String REMOTE_ENTRY_KEY = "remote_entry_key";
// Key to be used as an entry key for a credential entry
- private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+ public static final String CREDENTIAL_ENTRY_KEY = "credential_key";
@NonNull
private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
@@ -82,7 +84,8 @@
private final ProviderResponseDataHandler mProviderResponseDataHandler;
/** Creates a new provider session to be used by the request session. */
- @Nullable public static ProviderGetSession createNewSession(
+ @Nullable
+ public static ProviderGetSession createNewSession(
Context context,
@UserIdInt int userId,
CredentialProviderInfo providerInfo,
@@ -113,6 +116,7 @@
Log.i(TAG, "Unable to create provider session");
return null;
}
+
private static BeginGetCredentialRequest constructQueryPhaseRequest(
android.credentials.GetCredentialRequest filteredRequest,
CallingAppInfo callingAppInfo,
@@ -169,7 +173,7 @@
CallingAppInfo callingAppInfo,
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap,
String hybridService) {
- super(context, beginGetRequest, callbacks, info.getComponentName() ,
+ super(context, beginGetRequest, callbacks, info.getComponentName(),
userId, remoteCredentialService);
mCompleteRequest = completeGetRequest;
mCallingAppInfo = callingAppInfo;
@@ -191,6 +195,7 @@
if (exception instanceof GetCredentialException) {
mProviderException = (GetCredentialException) exception;
}
+ captureCandidateFailure();
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -269,13 +274,14 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ startCandidateMetrics();
mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
}
}
@Override // Call from request session to data to be shown on the UI
- @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+ @Nullable
+ protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
@@ -382,6 +388,7 @@
GetCredentialException exception = maybeGetPendingIntentException(
providerPendingIntentResponse);
if (exception != null) {
+ // TODO (b/271135048), for AuthenticationEntry callback selection, set error
invokeCallbackWithError(exception.getType(),
exception.getMessage());
// Additional content received is in the form of an exception which ends the flow.
@@ -427,13 +434,39 @@
private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
mProviderResponse = response;
addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
+ // Log the data.
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
return;
}
+ gatherCandidateEntryMetrics(response);
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
+ private void gatherCandidateEntryMetrics(BeginGetCredentialResponse response) {
+ try {
+ int numCredEntries = response.getCredentialEntries().size();
+ int numActionEntries = response.getActions().size();
+ int numAuthEntries = response.getAuthenticationActions().size();
+ // TODO immediately add remote entries
+ // TODO immediately confirm how to get types from slice to get unique type count via
+ // dedupe
+ response.getCredentialEntries().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+ response.getActions().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
+ response.getAuthenticationActions().forEach(c ->
+ mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
+ mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
+ + numActionEntries);
+ mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
+ mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
+ mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
/**
* When an invalid state occurs, e.g. entry mismatch or no response from provider,
* we send back a TYPE_NO_CREDENTIAL error as to the developer.
@@ -461,11 +494,12 @@
.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
|| e.second.getStatus()
== AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
- );
+ );
}
private class ProviderResponseDataHandler {
- @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+ @Nullable
+ private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
new HashMap<>();
@@ -475,7 +509,8 @@
private final Map<String, Pair<Action, AuthenticationEntry>> mUiAuthenticationEntries =
new HashMap<>();
- @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+ @Nullable
+ private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -499,6 +534,7 @@
setRemoteEntry(remoteEntry);
}
}
+
public void addCredentialEntry(CredentialEntry credentialEntry) {
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
@@ -549,7 +585,6 @@
}
-
public GetCredentialProviderData toGetCredentialProviderData() {
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(prepareActionEntries())
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 03e2a32..faa91dc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -32,12 +32,14 @@
import android.util.Log;
import com.android.server.credentials.metrics.CandidatePhaseMetric;
+import com.android.server.credentials.metrics.InitialPhaseMetric;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.UUID;
/**
* Provider session storing the state of provider response and ui entries.
+ *
* @param <T> The request to be sent to the provider
* @param <R> The response to be expected from the provider
*/
@@ -46,21 +48,36 @@
private static final String TAG = "ProviderSession";
- @NonNull protected final Context mContext;
- @NonNull protected final ComponentName mComponentName;
- @Nullable protected final CredentialProviderInfo mProviderInfo;
- @Nullable protected final RemoteCredentialService mRemoteCredentialService;
- @NonNull protected final int mUserId;
- @NonNull protected Status mStatus = Status.NOT_STARTED;
- @Nullable protected final ProviderInternalCallback mCallbacks;
- @Nullable protected Credential mFinalCredentialResponse;
- @Nullable protected ICancellationSignal mProviderCancellationSignal;
- @NonNull protected final T mProviderRequest;
- @Nullable protected R mProviderResponse;
- @NonNull protected Boolean mProviderResponseSet = false;
+ @NonNull
+ protected final Context mContext;
+ @NonNull
+ protected final ComponentName mComponentName;
+ @Nullable
+ protected final CredentialProviderInfo mProviderInfo;
+ @Nullable
+ protected final RemoteCredentialService mRemoteCredentialService;
+ @NonNull
+ protected final int mUserId;
+ @NonNull
+ protected Status mStatus = Status.NOT_STARTED;
+ @Nullable
+ protected final ProviderInternalCallback mCallbacks;
+ @Nullable
+ protected Credential mFinalCredentialResponse;
+ @Nullable
+ protected ICancellationSignal mProviderCancellationSignal;
+ @NonNull
+ protected final T mProviderRequest;
+ @Nullable
+ protected R mProviderResponse;
+ @NonNull
+ protected Boolean mProviderResponseSet = false;
// Specific candidate provider metric for the provider this session handles
- @Nullable protected CandidatePhaseMetric mCandidateProviderMetric;
- @NonNull private int mProviderSessionUid;
+ @NonNull
+ protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+ new CandidatePhaseMetric();
+ @NonNull
+ private int mProviderSessionUid;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
@@ -100,6 +117,7 @@
* Interface to be implemented by any class that wishes to get a callback when a particular
* provider session's status changes. Typically, implemented by the {@link RequestSession}
* class.
+ *
* @param <V> the type of the final response expected
*/
public interface ProviderInternalCallback<V> {
@@ -127,7 +145,6 @@
mUserId = userId;
mComponentName = componentName;
mRemoteCredentialService = remoteCredentialService;
- mCandidateProviderMetric = new CandidatePhaseMetric();
mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
}
@@ -158,7 +175,7 @@
}
public Credential getFinalCredentialResponse() {
- return mFinalCredentialResponse;
+ return mFinalCredentialResponse;
}
/** Propagates cancellation signal to the remote provider service. */
@@ -192,7 +209,13 @@
return mRemoteCredentialService;
}
- /** Updates the status .*/
+ protected void captureCandidateFailure() {
+ mCandidatePhasePerProviderMetric.setHasException(true);
+ // TODO(b/271135048) - this is a true exception, but what about the empty case?
+ // Add more nuance in next iteration.
+ }
+
+ /** Updates the status . */
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
updateCandidateMetric(status);
@@ -200,15 +223,37 @@
}
private void updateCandidateMetric(Status status) {
- mCandidateProviderMetric.setCandidateUid(mProviderSessionUid);
- mCandidateProviderMetric
- .setQueryFinishTimeNanoseconds(System.nanoTime());
- if (isTerminatingStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
- .getMetricCode());
- } else if (isCompletionStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
- .getMetricCode());
+ try {
+ mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
+ // TODO immediately update the candidate phase here to have more new data
+ mCandidatePhasePerProviderMetric
+ .setQueryFinishTimeNanoseconds(System.nanoTime());
+ if (isTerminatingStatus(status)) {
+ mCandidatePhasePerProviderMetric.setQueryReturned(false);
+ mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+ ProviderStatusForMetrics.QUERY_FAILURE
+ .getMetricCode());
+ } else if (isCompletionStatus(status)) {
+ mCandidatePhasePerProviderMetric.setQueryReturned(true);
+ mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+ ProviderStatusForMetrics.QUERY_SUCCESS
+ .getMetricCode());
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
+ // Common method to transfer metrics from the initial phase to the candidate phase per provider
+ protected void startCandidateMetrics() {
+ try {
+ InitialPhaseMetric initMetric = ((RequestSession) mCallbacks).mInitialPhaseMetric;
+ mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
+ mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
+ initMetric.getCredentialServiceStartedTimeNanoseconds());
+ mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
}
}
@@ -228,7 +273,8 @@
}
/** Update the response state stored with the provider session. */
- @Nullable protected R getProviderResponse() {
+ @Nullable
+ protected R getProviderResponse() {
return mProviderResponse;
}
@@ -265,15 +311,20 @@
return false;
}
- /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
- * shown on the UI. */
- @Nullable protected abstract ProviderData prepareUiData();
+ /**
+ * Should be overridden to prepare, and stores state for {@link ProviderData} to be
+ * shown on the UI.
+ */
+ @Nullable
+ protected abstract ProviderData prepareUiData();
/** Should be overridden to handle the selected entry from the UI. */
protected abstract void onUiEntrySelected(String entryType, String entryId,
ProviderPendingIntentResponse providerPendingIntentResponse);
- /** Should be overridden to invoke the provider at a defined location. Helpful for
- * situations such as metric generation. */
+ /**
+ * Should be overridden to invoke the provider at a defined location. Helpful for
+ * situations such as metric generation.
+ */
protected abstract void invokeSession();
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index edddba0..3ac10c9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -36,12 +36,15 @@
import com.android.internal.R;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
+import com.android.server.credentials.metrics.EntryEnum;
import com.android.server.credentials.metrics.InitialPhaseMetric;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -75,8 +78,16 @@
protected final CancellationSignal mCancellationSignal;
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
- protected ChosenProviderMetric mChosenProviderMetric = new ChosenProviderMetric();
- protected InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+ protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+ protected final ChosenProviderFinalPhaseMetric
+ mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+
+ // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
+ // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
+ @NonNull
+ protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
+ // As emits occur in sequential order, increment this counter and utilize
+ protected int mSequenceCounter = 0;
protected final String mHybridService;
@NonNull
@@ -112,9 +123,17 @@
mUserId, this);
mHybridService = context.getResources().getString(
R.string.config_defaultCredentialManagerHybridService);
- mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
- mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
- mInitialPhaseMetric.setCallerUid(mCallingUid);
+ initialPhaseMetricSetup(timestampStarted);
+ }
+
+ private void initialPhaseMetricSetup(long timestampStarted) {
+ try {
+ mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
+ mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
+ mInitialPhaseMetric.setCallerUid(mCallingUid);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
}
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -152,10 +171,26 @@
return;
}
Log.i(TAG, "Provider session found");
+ logBrowsingPhasePerSelect(selection, providerSession);
providerSession.onUiEntrySelected(selection.getEntryKey(),
selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
}
+ private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
+ ProviderSession providerSession) {
+ try {
+ CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+ browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
+ browsingPhaseMetric.setEntryEnum(
+ EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+ browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
+ .getCandidateUid());
+ this.mCandidateBrowsingPhaseMetric.add(new CandidateBrowsingPhaseMetric());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
+ }
+
protected void finishSession(boolean propagateCancellation) {
Log.i(TAG, "finishing session");
if (propagateCancellation) {
@@ -176,7 +211,14 @@
protected void logApiCall(ApiName apiName, ApiStatus apiStatus) {
logApiCalled(apiName, apiStatus, mProviders, mCallingUid,
- mChosenProviderMetric);
+ mChosenProviderFinalPhaseMetric);
+ }
+
+ protected void logApiCall(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+ List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics) {
+ // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
+ // For the returned types by authentication entries - i.e. a CandidatePhase During Browse
+ // TODO call MetricUtilities with new setup
}
protected boolean isSessionCancelled() {
@@ -203,6 +245,7 @@
Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
if (isSessionCancelled()) {
+ MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -218,11 +261,8 @@
}
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
- if (isSessionCancelled()) {
- Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
- } else {
- launchUiWithProviderData(providerDataList);
- }
+ MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+ launchUiWithProviderData(providerDataList);
}
}
@@ -232,12 +272,27 @@
* @param componentName the componentName to associate with a provider
*/
protected void setChosenMetric(ComponentName componentName) {
- CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
- .mCandidateProviderMetric;
- mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
- mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
- mChosenProviderMetric.setQueryPhaseLatencyMicroseconds(
- metric.getQueryLatencyMicroseconds());
- mChosenProviderMetric.setQueryStartTimeNanoseconds(metric.getStartQueryTimeNanoseconds());
+ try {
+ CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
+ .mCandidatePhasePerProviderMetric;
+
+ mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
+ mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
+
+ mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
+ metric.getQueryLatencyMicroseconds());
+
+ mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+ metric.getServiceBeganTimeNanoseconds());
+ mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+ metric.getStartQueryTimeNanoseconds());
+
+ // TODO immediately update with the entry count numbers from the candidate metrics
+ // TODO immediately add the exception bit for candidates and providers
+
+ mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 37ec8f0..0e1e0389 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -17,23 +17,22 @@
package com.android.server.credentials.metrics;
/**
- * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is
- * shown various entries from the provider responses, and may selectively browse through many
- * entries. It is possible that the initial set of browsing is for a provider that is ultimately
- * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can
- * understand where user interaction is more cumbersome, informing us for future improvements. This
- * can only be complete when the browsing is finished, ending in a final user choice, or possibly
- * a cancellation. Thus, this will be collected and emitted in the final phase, though collection
- * will begin in the candidate phase when the user begins browsing options.
+ * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderFinalPhaseMetric}.
+ * The user is shown various entries from the provider responses, and may selectively browse through
+ * many entries. It is possible that the initial set of browsing is for a provider that is
+ * ultimately not chosen. This metric will be gathered PER browsing click, and aggregated, so that
+ * we can understand where user interaction is more cumbersome, informing us for future
+ * improvements. This can only be complete when the browsing is finished, ending in a final user
+ * choice, or possibly a cancellation. Thus, this will be collected and emitted in the final phase,
+ * though collection will begin in the candidate phase when the user begins browsing options.
*/
public class CandidateBrowsingPhaseMetric {
- private static final String TAG = "CandidateSelectionPhaseMetric";
- private static final int SEQUENCE_ID = 3;
+ private static final String TAG = "CandidateBrowsingPhaseMetric";
// The session id associated with the API Call this candidate provider is a part of, default -1
private int mSessionId = -1;
- // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum).
- private int mEntryEnum = -1;
+ // The EntryEnum that was pressed, defaults to -1
+ private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode();
// The provider associated with the press, defaults to -1
private int mProviderUid = -1;
@@ -47,12 +46,6 @@
return mSessionId;
}
- /* -- The sequence ID -- */
-
- public int getSequenceId() {
- return SEQUENCE_ID;
- }
-
/* -- The Entry of this tap -- */
public void setEntryEnum(int entryEnum) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 1c7fb69..f00c7f4 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -20,6 +20,9 @@
import com.android.server.credentials.MetricUtilities;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* The central candidate provider metric object that mimics our defined metric setup.
* Some types are redundant across these metric collectors, but that has debug use-cases as
@@ -30,10 +33,8 @@
public class CandidatePhaseMetric {
private static final String TAG = "CandidateProviderMetric";
- // Since this will always be the second in the split sequence, this is statically 2
- private static final int SESSION_ID = 2;
- // The sequence number of this emit of the API call, default -1, equal for all candidates
- private int mSequenceId = -1;
+ // The session id of this provider, default set to -1
+ private int mSessionId = -1;
// Indicates if this provider returned from the query phase, default false
private boolean mQueryReturned = false;
@@ -68,6 +69,8 @@
private int mRemoteEntryCount = -1;
// The count of authentication entries from this provider, defaults to -1
private int mAuthenticationEntryCount = -1;
+ // Gathered to pass on to chosen provider when required
+ private List<EntryEnum> mAvailableEntries = new ArrayList<>();
public CandidatePhaseMetric() {
}
@@ -150,18 +153,13 @@
}
/* -------------- Session Id ---------------- */
+
+ public void setSessionId(int sessionId) {
+ mSessionId = sessionId;
+ }
+
public int getSessionId() {
- return SESSION_ID;
- }
-
- /* -------------- Sequence Id ---------------- */
-
- public void setSequenceId(int sequenceId) {
- mSequenceId = sequenceId;
- }
-
- public int getSequenceId() {
- return mSequenceId;
+ return mSessionId;
}
/* -------------- Query Returned Status ---------------- */
@@ -243,4 +241,28 @@
public int getAuthenticationEntryCount() {
return mAuthenticationEntryCount;
}
+
+ /* -------------- The Entries Gathered ---------------- */
+
+ /**
+ * Allows adding an entry record to this metric collector, which can then be propagated to
+ * the final phase to retain information on the data available to the candidate.
+ *
+ * @param e the entry enum collected by the candidate provider associated with this metric
+ * collector
+ */
+ public void addEntry(EntryEnum e) {
+ this.mAvailableEntries.add(e);
+ }
+
+ /**
+ * Returns a safely copied list of the entries captured by this metric collector associated
+ * with a particular candidate provider.
+ *
+ * @return the full collection of entries encountered by the candidate provider associated with
+ * this metric
+ */
+ public List<EntryEnum> getAvailableEntries() {
+ return new ArrayList<>(this.mAvailableEntries); // no alias copy
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
similarity index 80%
rename from services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
rename to services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 1a61091..32fe204 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -21,16 +21,22 @@
import com.android.server.credentials.MetricUtilities;
/**
- * The central chosen provider metric object that mimics our defined metric setup.
+ * The central chosen provider metric object that mimics our defined metric setup. This is used
+ * in the final phase of the flow and emits final status metrics.
* Some types are redundant across these metric collectors, but that has debug use-cases as
* these data-types are available at different moments of the flow (and typically, one can feed
* into the next).
* TODO(b/270403549) - iterate on this in V3+
+ * TODO(Immediately) - finalize V3 only types
*/
-public class ChosenProviderMetric {
+public class ChosenProviderFinalPhaseMetric {
- // TODO(b/270403549) - applies elsewhere, likely removed or replaced with a count-index (1,2,3)
- private static final String TAG = "ChosenProviderMetric";
+ // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index
+ private static final String TAG = "ChosenFinalPhaseMetric";
+ // The session id associated with this API call, used to unite split emits
+ private long mSessionId = -1;
+ // Reveals if the UI was returned, false by default
+ private boolean mUiReturned = false;
private int mChosenUid = -1;
// Latency figures typically fed in from prior CandidateProviderMetric
@@ -39,16 +45,29 @@
private int mQueryPhaseLatencyMicroseconds = -1;
// Timestamps kept in raw nanoseconds. Expected to be converted to microseconds from using
- // reference 'mServiceBeganTimeNanoseconds' during metric log point.
+ // reference 'mServiceBeganTimeNanoseconds' during metric log point
+ // Kept for local reference purposes, the initial timestamp of the service called passed in
private long mServiceBeganTimeNanoseconds = -1;
+ // The first query timestamp, which upon emit is normalized to microseconds using the reference
+ // start timestamp
private long mQueryStartTimeNanoseconds = -1;
+ // The UI call timestamp, which upon emit will be normalized to microseconds using reference
private long mUiCallStartTimeNanoseconds = -1;
+ // The UI return timestamp, which upon emit will be normalized to microseconds using reference
private long mUiCallEndTimeNanoseconds = -1;
+ // The final finish timestamp, which upon emit will be normalized to microseconds with reference
private long mFinalFinishTimeNanoseconds = -1;
- private int mChosenProviderStatus = -1;
+ // The status of this provider after selection
- public ChosenProviderMetric() {
+ // Other General Information, such as final api status, provider status, entry info, etc...
+
+ private int mChosenProviderStatus = -1;
+ // TODO add remaining properties based on the Atom ; specifically, migrate the candidate
+ // Entry information, and store final status here
+
+
+ public ChosenProviderFinalPhaseMetric() {
}
/* ------------------- UID ------------------- */
@@ -200,4 +219,24 @@
public void setChosenProviderStatus(int chosenProviderStatus) {
mChosenProviderStatus = chosenProviderStatus;
}
+
+ /* ----------- Session ID -------------- */
+
+ public void setSessionId(long sessionId) {
+ mSessionId = sessionId;
+ }
+
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ /* ----------- UI Returned Successfully -------------- */
+
+ public void setUiReturned(boolean uiReturned) {
+ mUiReturned = uiReturned;
+ }
+
+ public boolean isUiReturned() {
+ return mUiReturned;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
new file mode 100644
index 0000000..73403a6
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+import static com.android.server.credentials.ProviderGetSession.ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.AUTHENTICATION_ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.REMOTE_ENTRY_KEY;
+
+import android.util.Log;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public enum EntryEnum {
+ // TODO immediately, update with built entries
+ UNKNOWN(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ ACTION_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL),
+ CREDENTIAL_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL),
+ REMOTE_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL),
+ AUTHENTICATION_ENTRY(
+ CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
+ );
+
+ private static final String TAG = "EntryEnum";
+
+ private final int mInnerMetricCode;
+
+ private static final Map<String, Integer> sKeyToEntryCode = Map.ofEntries(
+ new AbstractMap.SimpleEntry<>(ACTION_ENTRY_KEY,
+ ACTION_ENTRY.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(AUTHENTICATION_ACTION_ENTRY_KEY,
+ AUTHENTICATION_ENTRY.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(REMOTE_ENTRY_KEY,
+ REMOTE_ENTRY.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(CREDENTIAL_ENTRY_KEY,
+ CREDENTIAL_ENTRY.mInnerMetricCode)
+ );
+
+ EntryEnum(int innerMetricCode) {
+ this.mInnerMetricCode = innerMetricCode;
+ }
+
+ /**
+ * Gives the West-world version of the metric name.
+ *
+ * @return a code corresponding to the west world metric name
+ */
+ public int getMetricCode() {
+ return this.mInnerMetricCode;
+ }
+
+ /**
+ * Given a string key type known to the framework, this returns the known metric code associated
+ * with that string.
+ *
+ * @param stringKey a string key type for a particular entry
+ * @return the metric code associated with this enum
+ */
+ public static int getMetricCodeFromString(String stringKey) {
+ if (!sKeyToEntryCode.containsKey(stringKey)) {
+ Log.w(TAG, "Attempted to use an unsupported string key entry type");
+ return UNKNOWN.mInnerMetricCode;
+ }
+ return sKeyToEntryCode.get(stringKey);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 31c6f6f..a73495f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -24,16 +24,14 @@
* TODO(b/270403549) - iterate on this in V3+
*/
public class InitialPhaseMetric {
- private static final String TAG = "PreCandidateMetric";
- // A sequence id to order united emits, due to split, this will statically always be 1
- public static final int SEQUENCE_ID = 1;
+ private static final String TAG = "InitialPhaseMetric";
// The api being called, default set to unknown
private int mApiName = ApiName.UNKNOWN.getMetricCode();
// The caller uid of the calling application, default to -1
private int mCallerUid = -1;
// The session id to unite multiple atom emits, default to -1
- private long mSessionId = -1;
+ private int mSessionId = -1;
private int mCountRequestClassType = -1;
// Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
@@ -100,15 +98,14 @@
/* ------ SessionId ------ */
- public void setSessionId(long sessionId) {
+ public void setSessionId(int sessionId) {
mSessionId = sessionId;
}
- public long getSessionId() {
+ public int getSessionId() {
return mSessionId;
}
-
/* ------ Count Request Class Types ------ */
public void setCountRequestClassType(int countRequestClassType) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index ec101cd..7eeb51c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,10 +16,11 @@
package com.android.server.devicepolicy;
-import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
-import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION;
+import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
@@ -27,6 +28,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyState;
import android.app.admin.PolicyKey;
@@ -46,6 +48,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
@@ -79,6 +82,10 @@
final class DevicePolicyEngine {
static final String TAG = "DevicePolicyEngine";
+ private static final String CELLULAR_2G_USER_RESTRICTION_ID =
+ DevicePolicyIdentifiers.getIdentifierForUserRestriction(
+ UserManager.DISALLOW_CELLULAR_2G);
+
private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = true;
@@ -380,6 +387,15 @@
Objects.requireNonNull(value);
synchronized (mLock) {
+ // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code
+ // that honors the restriction once there's an API available
+ if (checkFor2gFailure(policyDefinition, enforcingAdmin)) {
+ Log.i(TAG,
+ "Device does not support capabilities required to disable 2g. Not setting"
+ + " global policy state.");
+ return;
+ }
+
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
@@ -1162,6 +1178,36 @@
DEFAULT_ENABLE_COEXISTENCE_FLAG);
}
+ private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition,
+ @NonNull EnforcingAdmin enforcingAdmin) {
+ if (!policyDefinition.getPolicyKey().getIdentifier().equals(
+ CELLULAR_2G_USER_RESTRICTION_ID)) {
+ return false;
+ }
+
+ boolean isCapabilitySupported;
+ try {
+ isCapabilitySupported = mContext.getSystemService(
+ TelephonyManager.class).isRadioInterfaceCapabilitySupported(
+ TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK);
+ } catch (IllegalStateException e) {
+ // isRadioInterfaceCapabilitySupported can throw if there is no Telephony
+ // service initialized.
+ isCapabilitySupported = false;
+ }
+
+ if (!isCapabilitySupported) {
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ RESULT_FAILURE_HARDWARE_LIMITATION,
+ UserHandle.USER_ALL);
+ return true;
+ }
+
+ return false;
+ }
+
private class DevicePoliciesReaderWriter {
private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
@@ -1316,8 +1362,8 @@
if (adminsPolicy != null) {
mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
} else {
- Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
- + "AdminsPolicy.");
+ Log.e(TAG,
+ "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
}
}
@@ -1328,8 +1374,8 @@
if (adminsPolicy != null) {
mGlobalPolicies.put(policyKey, adminsPolicy);
} else {
- Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
- + "AdminsPolicy.");
+ Log.e(TAG,
+ "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6cd9f1c..720533f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -95,6 +95,7 @@
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
@@ -3224,7 +3225,7 @@
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
Bundle options = new BroadcastOptions()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
- .setDeferUntilActive(true)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
mInjector.binderWithCleanCallingIdentity(() ->
mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle), null, options));
@@ -15440,7 +15441,8 @@
Slogf.i(LOG_TAG, "Sending %s broadcast to manifest receivers.", intent.getAction());
broadcastIntentToCrossProfileManifestReceivers(
intent, parentHandle, requiresPermission);
- broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle);
+ broadcastExplicitIntentToRoleHolder(
+ intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, parentHandle);
}
@Override
@@ -15481,36 +15483,6 @@
}
}
- private void broadcastIntentToDevicePolicyManagerRoleHolder(
- Intent intent, UserHandle userHandle) {
- final int userId = userHandle.getIdentifier();
- final String packageName = getDevicePolicyManagementRoleHolderPackageName(mContext);
- if (packageName == null) {
- return;
- }
- try {
- final Intent packageIntent = new Intent(intent)
- .setPackage(packageName);
- final List<ResolveInfo> receivers = mIPackageManager.queryIntentReceivers(
- packageIntent,
- /* resolvedType= */ null,
- STOCK_PM_FLAGS,
- userId).getList();
- if (receivers.isEmpty()) {
- return;
- }
- for (ResolveInfo receiver : receivers) {
- final Intent componentIntent = new Intent(packageIntent)
- .setComponent(receiver.getComponentInfo().getComponentName())
- .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mContext.sendBroadcastAsUser(componentIntent, userHandle);
- }
- } catch (RemoteException ex) {
- Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
- intent.getAction(), ex);
- }
- }
-
/**
* Checks whether the package {@code packageName} has the {@code MODIFY_QUIET_MODE}
* permission granted for the user {@code userId}.
@@ -20718,7 +20690,7 @@
private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) {
String devicePolicyManagerRoleHolderPackageName =
- getDevicePolicyManagementRoleHolderPackageName(mContext);
+ getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
if (devicePolicyManagerRoleHolderPackageName == null) {
Slogf.d(LOG_TAG, "No device policy management role holder specified.");
return;
@@ -20744,14 +20716,24 @@
}
}
+ /**
+ * If multiple packages hold the role, returns the first package in the list.
+ */
+ @Nullable
+ private String getRoleHolderPackageName(Context context, String role) {
+ return getRoleHolderPackageNameOnUser(context, role, Process.myUserHandle());
+ }
- private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
+ /**
+ * If multiple packages hold the role, returns the first package in the list.
+ */
+ @Nullable
+ private String getRoleHolderPackageNameOnUser(Context context, String role, UserHandle user) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
// Calling identity needs to be cleared as this method is used in the permissions checks.
return mInjector.binderWithCleanCallingIdentity(() -> {
- List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+ List<String> roleHolders = roleManager.getRoleHoldersAsUser(role, user);
if (roleHolders.isEmpty()) {
return null;
}
@@ -20762,7 +20744,7 @@
private boolean isCallerDevicePolicyManagementRoleHolder(CallerIdentity caller) {
int callerUid = caller.getUid();
String devicePolicyManagementRoleHolderPackageName =
- getDevicePolicyManagementRoleHolderPackageName(mContext);
+ getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
int roleHolderUid = mInjector.getPackageManagerInternal().getPackageUid(
devicePolicyManagementRoleHolderPackageName, 0, caller.getUserId());
@@ -21830,15 +21812,23 @@
}
public void register() {
- mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM);
+ mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
}
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
- if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+ if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+ handleDevicePolicyManagementRoleChange(user);
return;
}
- String newRoleHolder = getRoleHolder();
+ if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
+ handleFinancedDeviceKioskRoleChange();
+ return;
+ }
+ }
+
+ private void handleDevicePolicyManagementRoleChange(UserHandle user) {
+ String newRoleHolder = getDeviceManagementRoleHolder(user);
if (isDefaultRoleHolder(newRoleHolder)) {
Slogf.i(LOG_TAG,
"onRoleHoldersChanged: Default role holder is set, returning early");
@@ -21873,9 +21863,42 @@
}
}
- private String getRoleHolder() {
- return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName(
- mContext);
+ private void handleFinancedDeviceKioskRoleChange() {
+ if (!isDevicePolicyEngineEnabled()) {
+ return;
+ }
+ Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED);
+ Intent intent = new Intent(ACTION_DEVICE_FINANCING_STATE_CHANGED);
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ for (UserInfo userInfo : mUserManager.getUsers()) {
+ UserHandle user = userInfo.getUserHandle();
+ broadcastExplicitIntentToRoleHolder(
+ intent, RoleManager.ROLE_SYSTEM_SUPERVISION, user);
+ broadcastExplicitIntentToRoleHolder(
+ intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
+ ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(user.getIdentifier());
+ if (admin == null) {
+ continue;
+ }
+ if (!isProfileOwnerOfOrganizationOwnedDevice(
+ admin.info.getComponent(), user.getIdentifier())
+ && !isDeviceOwner(admin)) {
+ continue;
+ }
+ // Don't send the broadcast twice if the DPC is the same package as the
+ // DMRH
+ if (admin.info.getPackageName().equals(getDeviceManagementRoleHolder(user))) {
+ continue;
+ }
+ broadcastExplicitIntentToPackage(
+ intent, admin.info.getPackageName(), admin.getUserHandle());
+ }
+ });
+ }
+
+ private String getDeviceManagementRoleHolder(UserHandle user) {
+ return DevicePolicyManagerService.this.getRoleHolderPackageNameOnUser(
+ mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
}
private boolean isDefaultRoleHolder(String packageName) {
@@ -21935,6 +21958,40 @@
}
}
+ private void broadcastExplicitIntentToRoleHolder(
+ Intent intent, String role, UserHandle userHandle) {
+ String packageName = getRoleHolderPackageNameOnUser(mContext, role, userHandle);
+ if (packageName == null) {
+ return;
+ }
+ broadcastExplicitIntentToPackage(intent, packageName, userHandle);
+ }
+
+ private void broadcastExplicitIntentToPackage(
+ Intent intent, String packageName, UserHandle userHandle) {
+ int userId = userHandle.getIdentifier();
+ if (packageName == null) {
+ return;
+ }
+ Intent packageIntent = new Intent(intent)
+ .setPackage(packageName);
+ List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ packageIntent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ userId);
+ if (receivers.isEmpty()) {
+ Slog.i(LOG_TAG, "Found no receivers to handle intent " + intent
+ + " in package " + packageName);
+ return;
+ }
+ for (ResolveInfo receiver : receivers) {
+ Intent componentIntent = new Intent(packageIntent)
+ .setComponent(receiver.getComponentInfo().getComponentName())
+ .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mContext.sendBroadcastAsUser(componentIntent, userHandle);
+ }
+ }
+
@Override
public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
index 50113fe..b895812 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Handler;
@@ -27,6 +28,7 @@
import android.provider.Settings;
import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -41,6 +43,7 @@
private final UserManagerInternal mUmi;
private final ActivityManagerService mAms;
+ private final PackageManagerService mPms;
private final ContentResolver mContentResolver;
private final ContentObserver mDeviceProvisionedObserver =
@@ -63,20 +66,23 @@
/** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
- ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+ PackageManagerService pms, ContentResolver contentResolver,
+ boolean shouldAlwaysHaveMainUser) {
if (!UserManager.isHeadlessSystemUserMode()) {
return null;
}
return new HsumBootUserInitializer(
LocalServices.getService(UserManagerInternal.class),
- am, contentResolver, shouldAlwaysHaveMainUser);
+ am, pms, contentResolver, shouldAlwaysHaveMainUser);
}
private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
- ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+ PackageManagerService pms, ContentResolver contentResolver,
+ boolean shouldAlwaysHaveMainUser) {
mUmi = umi;
mAms = am;
+ mPms = pms;
mContentResolver = contentResolver;
mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
}
@@ -131,7 +137,8 @@
try {
t.traceBegin("getBootUser");
- final int bootUser = mUmi.getBootUser();
+ final int bootUser = mUmi.getBootUser(/* waitUntilSet= */ mPms
+ .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, /* version= */0));
t.traceEnd();
t.traceBegin("switchToBootUser-" + bootUser);
switchToBootUser(bootUser);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4c31645..b933508 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1793,6 +1793,10 @@
}
t.traceEnd();
+ t.traceBegin("StartAppHibernationService");
+ mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("ArtManagerLocal");
DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
t.traceEnd();
@@ -2316,10 +2320,6 @@
mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
t.traceEnd();
- t.traceBegin("StartAppHibernationService");
- mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
- t.traceEnd();
-
if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
t.traceBegin("StartGestureLauncher");
mSystemServiceManager.startService(GestureLauncherService.class);
@@ -2721,7 +2721,7 @@
// on it in their setup, but likely needs to be done after LockSettingsService is ready.
final HsumBootUserInitializer hsumBootUserInitializer =
HsumBootUserInitializer.createInstance(
- mActivityManagerService, mContentResolver,
+ mActivityManagerService, mPackageManagerService, mContentResolver,
context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
if (hsumBootUserInitializer != null) {
t.traceBegin("HsumBootUserInitializer.init");
@@ -3021,8 +3021,7 @@
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
t.traceEnd();
- if (hsumBootUserInitializer != null && !isAutomotive) {
- // TODO(b/261924826): remove isAutomotive check once the workflow is finalized
+ if (hsumBootUserInitializer != null) {
t.traceBegin("HsumBootUserInitializer.systemRunning");
hsumBootUserInitializer.systemRunning(t);
t.traceEnd();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 7e44049..7d4f87d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -17,6 +17,7 @@
package com.android.server.inputmethod;
import static android.inputmethodservice.InputMethodService.IME_ACTIVE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
@@ -35,11 +36,16 @@
import static org.mockito.Mockito.verify;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,8 +66,8 @@
super.setUp();
mVisibilityApplier =
(DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
- mInputMethodManagerService.mCurFocusedWindowClient = mock(
- InputMethodManagerService.ClientState.class);
+ mInputMethodManagerService.setAttachedClientForTesting(
+ mock(InputMethodManagerService.ClientState.class));
}
@Test
@@ -119,4 +125,38 @@
mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
}
+
+ @Test
+ public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
+ // Init a IME target client on the secondary display to show IME.
+ mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
+ 10 /* selfReportedDisplayId */);
+ mInputMethodManagerService.setAttachedClientForTesting(null);
+ startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+ synchronized (ImfLock.class) {
+ final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+ // Verify hideIme will apply the expected displayId when the default IME
+ // visibility applier app STATE_HIDE_IME.
+ mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
+ verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+ eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+ }
+ }
+
+ private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
+ return mInputMethodManagerService.startInputOrWindowGainedFocus(
+ StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
+ mMockInputMethodClient /* client */,
+ windowToken /* windowToken */,
+ StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR,
+ softInputMode /* softInputMode */,
+ 0 /* windowFlags */,
+ mEditorInfo /* editorInfo */,
+ mMockRemoteInputConnection /* inputConnection */,
+ mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
+ mTargetSdkVersion /* unverifiedTargetSdkVersion */,
+ mCallingUserId /* userId */,
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
index 2894395..24e380c 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r1000",
+ name: "FrameworksServicesTests_install_uses_sdk_r10000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r1000.xml",
+ manifest: "AndroidManifest-r10000.xml",
}
android_test_helper_app {
@@ -44,9 +44,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
+ name: "FrameworksServicesTests_install_uses_sdk_r0_s10000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r0-s1000.xml",
+ manifest: "AndroidManifest-r0-s10000.xml",
}
android_test_helper_app {
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
index 25743b8..383e60a 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
@@ -19,7 +19,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This fails because 31 is not version 5 -->
<extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
- <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
+ <extension-sdk android:sdkVersion="31" android:minExtensionVersion="10000" />
</uses-sdk>
<application>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
index 9bf9254..fe7a212 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
@@ -18,7 +18,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This will fail to install, because minExtensionVersion is not met -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
+ <extension-sdk android:sdkVersion="30" android:minExtensionVersion="10000" />
</uses-sdk>
<application>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index e711cab..1146271 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -127,10 +127,10 @@
":FrameworksServicesTests_install_uses_sdk_q0",
":FrameworksServicesTests_install_uses_sdk_q0_r0",
":FrameworksServicesTests_install_uses_sdk_r0",
- ":FrameworksServicesTests_install_uses_sdk_r1000",
+ ":FrameworksServicesTests_install_uses_sdk_r10000",
":FrameworksServicesTests_install_uses_sdk_r_none",
":FrameworksServicesTests_install_uses_sdk_r0_s0",
- ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
+ ":FrameworksServicesTests_install_uses_sdk_r0_s10000",
":FrameworksServicesTests_keyset_permdef_sa_unone",
":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index ebf309f..906cc83 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,10 @@
assertEquals(0, minExtVers.get(31, -1));
Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
- appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
+ appToError.put(Pair.create("install_uses_sdk.apk_r10000", R.raw.install_uses_sdk_r10000),
PackageManager.INSTALL_FAILED_OLDER_SDK);
appToError.put(
- Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+ Pair.create("install_uses_sdk.apk_r0_s10000", R.raw.install_uses_sdk_r0_s10000),
PackageManager.INSTALL_FAILED_OLDER_SDK);
appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 5377ee7..3a47b47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -50,7 +50,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -624,8 +623,7 @@
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
- verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+ verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
}
@Test
@@ -643,8 +641,7 @@
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
- verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+ verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
// The device configuration doesn't require a motion sensor to proceed with idling.
// This should be the case on TVs or other such devices. We should set an alarm to move
// forward if the motion sensor is missing in this case.
@@ -669,8 +666,7 @@
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
- verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+ verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
// The device configuration requires a motion sensor to proceed with idling,
// so we should never set an alarm to move forward if the motion sensor is
// missing in this case.
@@ -699,7 +695,7 @@
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT));
enterDeepState(STATE_ACTIVE);
setQuickDozeEnabled(true);
@@ -709,7 +705,7 @@
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController).scheduleAlarmLocked(
- eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT));
}
@Test
@@ -736,59 +732,56 @@
setScreenOn(false);
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
enterDeepState(STATE_INACTIVE);
setQuickDozeEnabled(true);
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
enterDeepState(STATE_IDLE_PENDING);
setQuickDozeEnabled(true);
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
enterDeepState(STATE_SENSING);
setQuickDozeEnabled(true);
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
enterDeepState(STATE_LOCATING);
setQuickDozeEnabled(true);
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
inOrder.verify(mDeviceIdleController)
- .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+ .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
// IDLE should stay as IDLE.
enterDeepState(STATE_IDLE);
// Clear out any alarm setting from the order before checking for this section.
- inOrder.verify(mDeviceIdleController, atLeastOnce())
- .scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
setQuickDozeEnabled(true);
verifyStateConditions(STATE_IDLE);
- inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
// IDLE_MAINTENANCE should stay as IDLE_MAINTENANCE.
enterDeepState(STATE_IDLE_MAINTENANCE);
// Clear out any alarm setting from the order before checking for this section.
- inOrder.verify(mDeviceIdleController, atLeastOnce())
- .scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
setQuickDozeEnabled(true);
verifyStateConditions(STATE_IDLE_MAINTENANCE);
- inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
// State is already QUICK_DOZE_DELAY. No work should be done.
enterDeepState(STATE_QUICK_DOZE_DELAY);
// Clear out any alarm setting from the order before checking for this section.
- inOrder.verify(mDeviceIdleController, atLeastOnce())
- .scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
setQuickDozeEnabled(true);
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_QUICK_DOZE_DELAY);
- inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+ inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
}
@Test
@@ -2685,17 +2678,12 @@
if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
enterDeepState(STATE_IDLE);
long now = SystemClock.elapsedRealtime();
- long alarm = mDeviceIdleController.getNextAlarmTime();
mDeviceIdleController.setIdleStartTimeForTest(
now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
- long newAlarm = mDeviceIdleController.getNextAlarmTime();
- assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
- newAlarm == alarm);
+ verifyStateConditions(STATE_IDLE);
mDeviceIdleController.setIdleStartTimeForTest(
now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
- newAlarm = mDeviceIdleController.getNextAlarmTime();
- assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
- (newAlarm - now) < minuteInMillis);
+ verifyStateConditions(STATE_IDLE_MAINTENANCE);
mDeviceIdleController.resetPreIdleTimeoutMode();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 83441bf..1a75170 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -68,6 +68,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* Test RescueParty.
@@ -94,6 +95,9 @@
"persist.device_config.configuration.disable_rescue_party";
private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
"persist.device_config.configuration.disable_rescue_party_factory_reset";
+ private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
+
+ private static final int THROTTLING_DURATION_MIN = 10;
private MockitoSession mSession;
private HashMap<String, String> mSystemSettingsMap;
@@ -459,6 +463,53 @@
}
@Test
+ public void testThrottlingOnBootFailures() {
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+ SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertFalse(RescueParty.isAttemptingFactoryReset());
+ }
+
+ @Test
+ public void testThrottlingOnAppCrash() {
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+ SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertFalse(RescueParty.isAttemptingFactoryReset());
+ }
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnBootFailures() {
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+ SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertTrue(RescueParty.isAttemptingFactoryReset());
+ }
+ @Test
+ public void testNotThrottlingAfterTimeoutOnAppCrash() {
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+ SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertTrue(RescueParty.isAttemptingFactoryReset());
+ }
+
+ @Test
public void testNativeRescuePartyResets() {
doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
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 b395f42..1741411 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -28,7 +28,6 @@
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
-import static android.app.AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT;
import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AlarmManager.WINDOW_HEURISTIC;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -126,7 +125,6 @@
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
-import android.app.role.RoleManager;
import android.app.tare.EconomyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ContentResolver;
@@ -134,7 +132,6 @@
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
-import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
@@ -192,7 +189,6 @@
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -246,8 +242,6 @@
@Mock
private PackageManagerInternal mPackageManagerInternal;
@Mock
- private RoleManager mRoleManager;
- @Mock
private AppStateTrackerImpl mAppStateTracker;
@Mock
private AlarmManagerService.ClockReceiver mClockReceiver;
@@ -393,11 +387,6 @@
}
@Override
- void registerContentObserver(ContentObserver observer, Uri uri) {
- // Do nothing.
- }
-
- @Override
void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
// Do nothing.
// The tests become flaky with an error message of
@@ -484,10 +473,12 @@
doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
() -> PermissionChecker.checkPermissionForPreflight(any(),
eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString()));
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ () -> PermissionChecker.checkPermissionForPreflight(any(), eq(SCHEDULE_EXACT_ALARM),
+ anyInt(), anyInt(), anyString()));
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
- when(mMockContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager);
registerAppIds(new String[]{TEST_CALLING_PACKAGE},
new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -1303,7 +1294,8 @@
final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
actualOptions.getDeliveryGroupPolicy());
- assertTrue(actualOptions.isDeferUntilActive());
+ assertEquals(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE,
+ actualOptions.getDeferralPolicy());
}
@Test
@@ -2180,40 +2172,6 @@
}
}
- @Test
- public void hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- mockScheduleExactAlarmState(true, false, MODE_DEFAULT);
- assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
- assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, false, MODE_IGNORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- }
-
- @Test
- public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- mockScheduleExactAlarmState(true, true, MODE_ERRORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, true, MODE_IGNORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmState(true, true, MODE_ALLOWED);
- assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- }
-
private void mockChangeEnabled(long changeId, boolean enabled) {
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
any(UserHandle.class)));
@@ -2221,16 +2179,62 @@
}
@Test
- public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException {
+ public void hasScheduleExactAlarmBinderCall() throws RemoteException {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+ mockScheduleExactAlarmState(true);
+ assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmState(false);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+ }
+
+ @Test
+ public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmState(false, false, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+ assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmState(false, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+ }
+
+ @Test
+ public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+
+ mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmState(false, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+ assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+ }
+
+ @Test
+ public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+
+ mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+ assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+ mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@@ -2239,61 +2243,94 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
// canScheduleExactAlarms should be true regardless of any permission state.
- mockUseExactAlarmState(true);
+ // Both SEA and UEA are denied in setUp.
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
mockUseExactAlarmState(false);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
- mockScheduleExactAlarmState(false, true, MODE_DEFAULT);
- assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmState(false);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
}
@Test
- public void canScheduleExactAlarmsBinderCall() throws RemoteException {
+ public void canScheduleExactAlarmsBinderCallPreT() throws RemoteException {
// Policy permission is denied in setUp().
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
// No permission, no exemption.
- mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, no exemption.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Policy permission only, no exemption.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
mockUseExactAlarmState(false);
// User permission only, no exemption.
- mockScheduleExactAlarmState(true, false, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// User permission only, no exemption.
- mockScheduleExactAlarmState(true, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Both permissions and exemption.
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockUseExactAlarmState(true);
+ assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+ }
+
+ @Test
+ public void canScheduleExactAlarmsBinderCall() throws RemoteException {
+ // Both permissions are denied in setUp().
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+ // No permission, no exemption.
+ assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+ // Policy permission only, no exemption.
+ mockUseExactAlarmState(true);
+ assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+ mockUseExactAlarmState(false);
+
+ // User permission only, no exemption.
+ mockScheduleExactAlarmState(true);
+ assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+ // No permission, exemption.
+ mockScheduleExactAlarmState(false);
+ when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
+ assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+ // No permission, core uid exemption.
+ when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
+ doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
+ assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+ // Both permissions and core uid exemption.
+ mockScheduleExactAlarmState(true);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
}
@@ -2403,8 +2440,9 @@
@Test
public void alarmClockBinderCallWithSEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmState(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
@@ -2430,9 +2468,10 @@
public void alarmClockBinderCallWithUEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
mockUseExactAlarmState(true);
- mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+ mockScheduleExactAlarmState(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
@@ -2454,7 +2493,7 @@
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
- private void mockScheduleExactAlarmState(boolean declared, boolean denyList, int mode) {
+ private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
.thenReturn(requesters);
@@ -2469,6 +2508,20 @@
TEST_CALLING_PACKAGE)).thenReturn(mode);
}
+ private void mockScheduleExactAlarmState(boolean granted) {
+ String[] requesters = granted ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
+ when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
+ .thenReturn(requesters);
+ mService.refreshExactAlarmCandidates();
+
+ final int result = granted ? PermissionChecker.PERMISSION_GRANTED
+ : PermissionChecker.PERMISSION_HARD_DENIED;
+ doReturn(result).when(
+ () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext),
+ eq(SCHEDULE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID),
+ eq(TEST_CALLING_PACKAGE)));
+ }
+
private void mockUseExactAlarmState(boolean granted) {
final int result = granted ? PermissionChecker.PERMISSION_GRANTED
: PermissionChecker.PERMISSION_HARD_DENIED;
@@ -2482,7 +2535,7 @@
public void alarmClockBinderCallWithoutPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2503,8 +2556,9 @@
@Test
public void exactBinderCallWithSEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmState(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
0, alarmPi, null, null, null, null);
@@ -2528,9 +2582,10 @@
public void exactBinderCallWithUEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
mockUseExactAlarmState(true);
- mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+ mockScheduleExactAlarmState(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
0, alarmPi, null, null, null, null);
@@ -2554,7 +2609,7 @@
public void exactBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2574,7 +2629,7 @@
public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2600,7 +2655,7 @@
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
mockUseExactAlarmState(true);
- mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2624,7 +2679,7 @@
public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2705,7 @@
public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2700,7 +2755,7 @@
public void binderCallWithUserAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
@@ -3025,7 +3080,7 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3038,7 +3093,7 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
@@ -3051,7 +3106,7 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
@@ -3067,7 +3122,7 @@
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3327,7 +3382,7 @@
.putExtra(Intent.EXTRA_REPLACING, true);
mockUseExactAlarmState(false);
- mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3335,7 +3390,7 @@
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(true);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3343,7 +3398,7 @@
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(false);
- mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3362,55 +3417,6 @@
}
@Test
- public void isScheduleExactAlarmAllowedByDefault() {
- final String package1 = "priv";
- final String package2 = "signed";
- final String package3 = "normal";
- final String package4 = "wellbeing";
- final int uid1 = 1294;
- final int uid2 = 8321;
- final int uid3 = 3412;
- final int uid4 = 4591;
-
- when(mPackageManagerInternal.isUidPrivileged(uid1)).thenReturn(true);
- when(mPackageManagerInternal.isUidPrivileged(uid2)).thenReturn(false);
- when(mPackageManagerInternal.isUidPrivileged(uid3)).thenReturn(false);
- when(mPackageManagerInternal.isUidPrivileged(uid4)).thenReturn(false);
-
- when(mPackageManagerInternal.isPlatformSigned(package1)).thenReturn(false);
- when(mPackageManagerInternal.isPlatformSigned(package2)).thenReturn(true);
- when(mPackageManagerInternal.isPlatformSigned(package3)).thenReturn(false);
- when(mPackageManagerInternal.isPlatformSigned(package4)).thenReturn(false);
-
- when(mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)).thenReturn(
- Arrays.asList(package4));
-
- mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
- mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
- package1,
- package3,
- });
-
- // 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.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
- package1,
- package3,
- });
-
- // Deny list doesn't matter now, only exemptions should be true.
- assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1));
- assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2));
- assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3));
- assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
- }
-
- @Test
public void alarmScheduledAtomPushed() {
for (int i = 0; i < 10; i++) {
final PendingIntent pi = getNewMockPendingIntent();
@@ -3509,7 +3515,7 @@
}
@Test
- public void hasUseExactAlarmPermission() {
+ public void hasUseExactAlarmInternal() {
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
mockUseExactAlarmState(true);
@@ -3520,7 +3526,7 @@
}
@Test
- public void hasUseExactAlarmPermissionChangeDisabled() {
+ public void hasUseExactAlarmInternalChangeDisabled() {
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, false);
mockUseExactAlarmState(true);
@@ -3531,6 +3537,49 @@
}
@Test
+ public void hasScheduleExactAlarmInternal() {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+ mockScheduleExactAlarmState(false);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmState(true);
+ assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ }
+
+ @Test
+ public void hasScheduleExactAlarmInternalPreT() {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+ mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
+
+ mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ }
+
+ @Test
+ public void hasScheduleExactAlarmInternalPreS() {
+ mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
+
+ mockScheduleExactAlarmState(true);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmState(false);
+ mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ }
+
+ @Test
public void temporaryQuotaReserve_hasQuota() {
final int quotaToFill = 5;
final String package1 = "package1";
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 9263bff..d56229c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -989,9 +989,16 @@
private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
int connectionGroup, int procState, long pss, long rss,
String processName, String packageName) {
+ return makeProcessRecord(pid, uid, packageUid, definingUid, connectionGroup,
+ procState, pss, rss, processName, packageName, mAms);
+ }
+
+ static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ int connectionGroup, int procState, long pss, long rss,
+ String processName, String packageName, ActivityManagerService ams) {
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
- ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+ ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
app.setPid(pid);
app.info.uid = packageUid;
if (definingUid != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 01e2768..2b6f217 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -25,6 +25,8 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
+import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import static org.junit.Assert.assertArrayEquals;
@@ -38,6 +40,7 @@
import android.app.ActivityManagerInternal;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
@@ -55,6 +58,7 @@
import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -86,6 +90,15 @@
private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3,
PACKAGE4};
+ private static final int SYSTEM_UID = android.os.Process.SYSTEM_UID;
+ private static final int APP_UID = android.os.Process.FIRST_APPLICATION_UID;
+
+ private static final BroadcastOptions OPT_DEFAULT = BroadcastOptions.makeBasic();
+ private static final BroadcastOptions OPT_NONE = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ private static final BroadcastOptions OPT_UNTIL_ACTIVE = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
@Mock ActivityManagerInternal mActivityManagerInternal;
@Mock BroadcastQueue mQueue;
@Mock ProcessRecord mProcess;
@@ -213,6 +226,75 @@
}
@Test
+ public void testCalculateUrgent() {
+ final Intent intent = new Intent();
+ final Intent intentForeground = new Intent()
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ assertFalse(calculateUrgent(intent, null));
+ assertTrue(calculateUrgent(intentForeground, null));
+
+ {
+ final BroadcastOptions opts = BroadcastOptions.makeBasic();
+ assertFalse(calculateUrgent(intent, opts));
+ }
+ {
+ final BroadcastOptions opts = BroadcastOptions.makeBasic();
+ opts.setInteractive(true);
+ assertTrue(calculateUrgent(intent, opts));
+ }
+ {
+ final BroadcastOptions opts = BroadcastOptions.makeBasic();
+ opts.setAlarmBroadcast(true);
+ assertTrue(calculateUrgent(intent, opts));
+ }
+ }
+
+ @Test
+ public void testCalculateDeferUntilActive_App() {
+ // Verify non-urgent behavior
+ assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, false));
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, false));
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, false));
+ assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+ // Verify urgent behavior
+ assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, true));
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, true));
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, true));
+ assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, true));
+ }
+
+ @Test
+ public void testCalculateDeferUntilActive_System() {
+ BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = true;
+
+ // Verify non-urgent behavior
+ assertTrue(calculateDeferUntilActive(SYSTEM_UID, null, null, false, false));
+ assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, false));
+ assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, false));
+ assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+ // Verify urgent behavior
+ assertFalse(calculateDeferUntilActive(SYSTEM_UID, null, null, false, true));
+ assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, true));
+ assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, true));
+ assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, true));
+ }
+
+ @Test
+ public void testCalculateDeferUntilActive_Overrides() {
+ final IIntentReceiver resultTo = new IIntentReceiver.Default();
+
+ // Ordered broadcasts never deferred; requested option is ignored
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, true, false));
+ assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, resultTo, true, false));
+
+ // Unordered with result is always deferred; requested option is ignored
+ assertTrue(calculateDeferUntilActive(APP_UID, OPT_NONE, resultTo, false, false));
+ }
+
+ @Test
public void testCleanupDisabledPackageReceivers() {
final int user0 = UserHandle.USER_SYSTEM;
final int user1 = user0 + 1;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
new file mode 100644
index 0000000..fd1b068
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static com.android.server.am.ApplicationExitInfoTest.makeProcessRecord;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Test class for the service timeout.
+ *
+ * Build/Install/Run:
+ * atest ServiceTimeoutTest
+ */
+@Presubmit
+public final class ServiceTimeoutTest {
+ private static final String TAG = ServiceTimeoutTest.class.getSimpleName();
+ private static final long DEFAULT_SERVICE_TIMEOUT = 2000;
+
+ @Rule
+ public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+
+ private ActivityManagerService mAms;
+ private ProcessList mProcessList;
+ private ActiveServices mActiveServices;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mProcessList = spy(new ProcessList());
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+
+ final ActivityManagerService realAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+ realAms.mPackageManagerInt = mPackageManagerInt;
+ realAms.mUsageStatsService = mUsageStatsManagerInt;
+ realAms.mProcessesReady = true;
+ realAms.mConstants.SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+ realAms.mConstants.SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+ mAms = spy(realAms);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ mHandlerThread.quit();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testServiceTimeoutAndProcessKill() throws Exception {
+ final int pid = 12345;
+ final int uid = 10123;
+ final String name = "com.example.foo";
+ final ProcessRecord app = makeProcessRecord(
+ pid, // pid
+ uid, // uid
+ uid, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_SERVICE, // procstate
+ 0, // pss
+ 0, // rss
+ name, // processName
+ name, // packageName
+ mAms);
+ app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+ mProcessList.updateLruProcessLocked(app, false, null);
+
+ final long now = SystemClock.uptimeMillis();
+ final ServiceRecord sr = spy(ServiceRecord.newEmptyInstanceForTest(mAms));
+ doNothing().when(sr).dump(any(), anyString());
+ sr.startRequested = true;
+ sr.executingStart = now;
+
+ app.mServices.startExecutingService(sr);
+ mActiveServices.scheduleServiceTimeoutLocked(app);
+
+ verify(mActiveServices, timeout(DEFAULT_SERVICE_TIMEOUT * 2).times(1))
+ .serviceTimeout(eq(app));
+
+ clearInvocations(mActiveServices);
+
+ app.mServices.startExecutingService(sr);
+ mActiveServices.scheduleServiceTimeoutLocked(app);
+
+ app.killLocked(TAG, 42, false);
+ mAms.removeLruProcessLocked(app);
+
+ verify(mActiveServices, after(DEFAULT_SERVICE_TIMEOUT * 4)
+ .times(1)).serviceTimeout(eq(app));
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+
+ @Override
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ if (mActiveServices == null) {
+ mActiveServices = spy(new ActiveServices(service));
+ }
+ return mActiveServices;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 8b420a3..e056417 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -50,6 +50,7 @@
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
+import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -91,6 +92,7 @@
public class JobSchedulerServiceTest {
private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
+ private static final int TEST_UID = 10123;
private JobSchedulerService mService;
@@ -177,6 +179,9 @@
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
+ mService.cancelJobsForUid(TEST_UID, true,
+ JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN,
+ "test cleanup");
}
private Clock getAdvancedClock(Clock clock, long incrementMs) {
@@ -257,9 +262,9 @@
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
- mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+ mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -279,26 +284,26 @@
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
grantRunUserInitiatedJobsPermission(true); // With permission
- assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(ConnectivityController.UNKNOWN_TIME)
.when(connectivityController).getEstimatedTransferTimeMs(any());
- assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
+ doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS / 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
- assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
+ doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(
- (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ (long) (mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
* 2 * mService.mConstants
- .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
+ .RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
+ doReturn(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
- assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
@@ -320,7 +325,7 @@
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
grantRunUserInitiatedJobsPermission(true);
- assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUIDT));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
@@ -1170,7 +1175,7 @@
i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
assertEquals("Got unexpected result for schedule #" + (i + 1),
expected,
- mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
+ mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
}
}
@@ -1191,7 +1196,7 @@
for (int i = 0; i < 500; ++i) {
assertEquals("Got unexpected result for schedule #" + (i + 1),
JobScheduler.RESULT_SUCCESS,
- mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
+ mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
}
}
@@ -1212,7 +1217,7 @@
for (int i = 0; i < 500; ++i) {
assertEquals("Got unexpected result for schedule #" + (i + 1),
JobScheduler.RESULT_SUCCESS,
- mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, "JSSTest",
+ mService.scheduleAsPackage(job, null, TEST_UID, "proxied.package", 0, "JSSTest",
""));
}
}
@@ -1236,11 +1241,63 @@
i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
assertEquals("Got unexpected result for schedule #" + (i + 1),
expected,
- mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(),
+ mService.scheduleAsPackage(job, null, TEST_UID,
+ job.getService().getPackageName(),
0, "JSSTest", ""));
}
}
+ /**
+ * Tests that the number of persisted JobWorkItems is capped.
+ */
+ @Test
+ public void testScheduleLimiting_JobWorkItems_Nonpersisted() {
+ mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
+ mService.mConstants.ENABLE_API_QUOTAS = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(false).build();
+ final JobWorkItem item = new JobWorkItem.Builder().build();
+ for (int i = 0; i < 1000; ++i) {
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ JobScheduler.RESULT_SUCCESS,
+ mService.scheduleAsPackage(job, item, TEST_UID,
+ job.getService().getPackageName(),
+ 0, "JSSTest", ""));
+ }
+ }
+
+ /**
+ * Tests that the number of persisted JobWorkItems is capped.
+ */
+ @Test
+ public void testScheduleLimiting_JobWorkItems_Persisted() {
+ mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
+ mService.mConstants.ENABLE_API_QUOTAS = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(true).build();
+ final JobWorkItem item = new JobWorkItem.Builder().build();
+ for (int i = 0; i < 500; ++i) {
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ JobScheduler.RESULT_SUCCESS,
+ mService.scheduleAsPackage(job, item, TEST_UID,
+ job.getService().getPackageName(),
+ 0, "JSSTest", ""));
+ }
+ try {
+ mService.scheduleAsPackage(job, item, TEST_UID, job.getService().getPackageName(),
+ 0, "JSSTest", "");
+ fail("Added more items than allowed");
+ } catch (IllegalStateException expected) {
+ // Success
+ }
+ }
+
/** Tests that jobs are removed from the pending list if the user stops the app. */
@Test
public void testUserStopRemovesPending() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 564893c..e7b3e6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -260,7 +260,7 @@
mUms.setBootUser(OTHER_USER_ID);
assertWithMessage("getBootUser")
- .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
@@ -273,7 +273,8 @@
mUms.setBootUser(PROFILE_USER_ID);
assertWithMessage("getBootUser")
- .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+ .isEqualTo(UserHandle.USER_SYSTEM);
}
@Test
@@ -289,7 +290,7 @@
// Boot user not switchable so return most recently in foreground.
assertWithMessage("getBootUser")
- .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
@@ -299,7 +300,8 @@
addUser(OTHER_USER_ID);
assertWithMessage("getBootUser")
- .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+ .isEqualTo(UserHandle.USER_SYSTEM);
}
@Test
@@ -312,14 +314,15 @@
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
assertWithMessage("getBootUser")
- .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
setSystemUserHeadless(true);
- assertThrows(UserManager.CheckedUserOperationException.class, () -> mUmi.getBootUser());
+ assertThrows(UserManager.CheckedUserOperationException.class,
+ () -> mUmi.getBootUser(/* waitUntilSet= */ false));
}
private void mockCurrentUser(@UserIdInt int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 239b6fd..70b5ac0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -596,6 +596,7 @@
.that(mMediator.assignUserToExtraDisplay(userId, displayId))
.isTrue();
expectUserIsVisibleOnDisplay(userId, displayId);
+ expectDisplaysAssignedToUserContainsDisplayId(userId, displayId);
if (unassign) {
Log.d(TAG, "Calling unassignUserFromExtraDisplay(" + userId + ", " + displayId + ")");
@@ -603,6 +604,7 @@
.that(mMediator.unassignUserFromExtraDisplay(userId, displayId))
.isTrue();
expectUserIsNotVisibleOnDisplay(userId, displayId);
+ expectDisplaysAssignedToUserDoesNotContainDisplayId(userId, displayId);
}
}
@@ -668,6 +670,7 @@
expectUserIsNotVisibleOnDisplay(userId, INVALID_DISPLAY);
expectUserIsNotVisibleOnDisplay(userId, SECONDARY_DISPLAY_ID);
expectUserIsNotVisibleOnDisplay(userId, OTHER_SECONDARY_DISPLAY_ID);
+ expectDisplaysAssignedToUserIsEmpty(userId);
}
protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
@@ -680,6 +683,24 @@
.that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
}
+ protected void expectDisplaysAssignedToUserContainsDisplayId(
+ @UserIdInt int userId, int displayId) {
+ expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+ .that(mMediator.getDisplaysAssignedToUser(userId)).asList().contains(displayId);
+ }
+
+ protected void expectDisplaysAssignedToUserDoesNotContainDisplayId(
+ @UserIdInt int userId, int displayId) {
+ expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+ .that(mMediator.getDisplaysAssignedToUser(userId)).asList()
+ .doesNotContain(displayId);
+ }
+
+ protected void expectDisplaysAssignedToUserIsEmpty(@UserIdInt int userId) {
+ expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+ .that(mMediator.getDisplaysAssignedToUser(userId)).isNull();
+ }
+
protected void expectUserCannotBeUnassignedFromDisplay(@UserIdInt int userId, int displayId) {
expectWithMessage("unassignUserFromExtraDisplay(%s, %s)", userId, displayId)
.that(mMediator.unassignUserFromExtraDisplay(userId, displayId)).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 71280ce..e81b63c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -403,6 +403,7 @@
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
pkgInfo.applicationInfo = applicationInfo;
- mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
+ mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), userId,
+ pkgInfo));
}
}
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 7642e7b..6c6b608 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
@@ -28,7 +28,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -109,7 +109,7 @@
device1Id).isNotEqualTo(device2Id);
- int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+ int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
deviceIds).asList().contains(device1Id);
assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
@@ -153,7 +153,7 @@
deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
int deviceId = mInputController.getInputDeviceId(deviceToken);
- int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+ int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
deviceIds).asList().contains(deviceId);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
index 8196d6a..e396263 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -52,7 +52,7 @@
private static final int STATE_WITHOUT_NOTIFICATION = 1;
private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
- private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+ private static final int STATE_WITH_ALL_NOTIFICATION = 3;
private static final int VALID_APP_UID = 1000;
private static final int INVALID_APP_UID = 2000;
@@ -68,6 +68,8 @@
private static final String CONTENT_2 = "content2:%1$s";
private static final String THERMAL_TITLE_2 = "thermal_title2";
private static final String THERMAL_CONTENT_2 = "thermal_content2";
+ private static final String POWER_SAVE_TITLE_2 = "power_save_title2";
+ private static final String POWER_SAVE_CONTENT_2 = "power_save_content2";
private DeviceStateNotificationController mController;
@@ -88,11 +90,12 @@
notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
new DeviceStateNotificationController.NotificationInfo(
NAME_1, TITLE_1, CONTENT_1,
- "", ""));
- notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+ "", "", "", ""));
+ notificationInfos.put(STATE_WITH_ALL_NOTIFICATION,
new DeviceStateNotificationController.NotificationInfo(
NAME_2, TITLE_2, CONTENT_2,
- THERMAL_TITLE_2, THERMAL_CONTENT_2));
+ THERMAL_TITLE_2, THERMAL_CONTENT_2,
+ POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2));
when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
@@ -139,10 +142,46 @@
}
@Test
+ public void test_powerSaveNotification() {
+ // Verify that the active notification is created.
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+ notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(Notification.FLAG_ONGOING_EVENT,
+ notification.flags & Notification.FLAG_ONGOING_EVENT);
+ Mockito.clearInvocations(mNotificationManager);
+
+ // Verify that the thermal critical notification is created.
+ mController.showPowerSaveNotificationIfNeeded(
+ STATE_WITH_ALL_NOTIFICATION);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ notification = mNotificationCaptor.getValue();
+ assertEquals(POWER_SAVE_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(POWER_SAVE_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+ // Verify that the notification is canceled.
+ mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
+ verify(mNotificationManager).cancel(
+ DeviceStateNotificationController.NOTIFICATION_TAG,
+ DeviceStateNotificationController.NOTIFICATION_ID);
+ }
+
+ @Test
public void test_thermalNotification() {
// Verify that the active notification is created.
mController.showStateActiveNotificationIfNeeded(
- STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+ STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
verify(mNotificationManager).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -157,7 +196,7 @@
// Verify that the thermal critical notification is created.
mController.showThermalCriticalNotificationIfNeeded(
- STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+ STATE_WITH_ALL_NOTIFICATION);
verify(mNotificationManager).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -168,7 +207,7 @@
assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
// Verify that the notification is canceled.
- mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+ mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
verify(mNotificationManager).cancel(
DeviceStateNotificationController.NOTIFICATION_TAG,
DeviceStateNotificationController.NOTIFICATION_ID);
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 6c73f71..851d8f9 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -39,10 +41,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
/**
* A collection of tests to exercise {@link DreamOverlayService}.
*/
@@ -60,6 +65,9 @@
@Mock
IDreamOverlayCallback mOverlayCallback;
+ @Mock
+ Executor mExecutor;
+
/**
* {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
* tracking interactions across {@link IDreamOverlay} binder interface. The service reports
@@ -78,8 +86,8 @@
private final Monitor mMonitor;
- TestDreamOverlayService(Monitor monitor) {
- super();
+ TestDreamOverlayService(Monitor monitor, Executor executor) {
+ super(executor);
mMonitor = monitor;
}
@@ -118,13 +126,63 @@
}
/**
+ * Verifies that callbacks for subclasses are run on the provided executor.
+ */
+ @Test
+ public void testCallbacksRunOnExecutor() throws RemoteException {
+ final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+ TestDreamOverlayService.Monitor.class);
+ final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
+ final IBinder binder = service.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+ final IDreamOverlayClient client = getClient(overlay);
+
+ // Start the dream.
+ client.startDream(mLayoutParams, mOverlayCallback,
+ FIRST_DREAM_COMPONENT.flattenToString(), false);
+
+ // The callback should not have run yet.
+ verify(monitor, never()).onStartDream();
+
+ // Run the Runnable sent to the executor.
+ ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mExecutor).execute(mRunnableCaptor.capture());
+ mRunnableCaptor.getValue().run();
+
+ // Callback is run.
+ verify(monitor).onStartDream();
+
+ // Verify onWakeUp is run on the executor.
+ client.wakeUp();
+ verify(monitor, never()).onWakeUp();
+ mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mExecutor).execute(mRunnableCaptor.capture());
+ mRunnableCaptor.getValue().run();
+ verify(monitor).onWakeUp();
+
+ // Verify onEndDream is run on the executor.
+ client.endDream();
+ verify(monitor, never()).onEndDream();
+ mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mExecutor).execute(mRunnableCaptor.capture());
+ mRunnableCaptor.getValue().run();
+ verify(monitor).onEndDream();
+ }
+
+ /**
* Verifies that only the currently started dream is able to affect the overlay.
*/
@Test
public void testOverlayClientInteraction() throws RemoteException {
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mExecutor).execute(any());
+
final TestDreamOverlayService.Monitor monitor = Mockito.mock(
TestDreamOverlayService.Monitor.class);
- final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
+ final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
final IBinder binder = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index d9d0715..64e6236 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -74,6 +74,8 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.crypto.SecretKey;
@@ -324,16 +326,30 @@
mInjected = mock(MockableRebootEscrowInjected.class);
mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
mKeyStoreManager, mStorage, mInjected);
- mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
thread.start();
mHandler = new Handler(thread.getLooper());
+ mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+
}
private void setServerBasedRebootEscrowProvider() throws Exception {
mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
mKeyStoreManager, mStorage, mInjected);
- mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
+ mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+ }
+
+ private void waitForHandler() throws InterruptedException {
+ // Wait for handler to complete processing.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(latch::countDown);
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+ }
+
+ private void callToRebootEscrowIfNeededAndWait(int userId) throws InterruptedException {
+ mService.callToRebootEscrowIfNeeded(userId, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ waitForHandler();
}
@Test
@@ -343,7 +359,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
}
@@ -355,8 +371,7 @@
mService.setRebootEscrowListener(mockListener);
mService.prepareRebootEscrow();
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
- verify(mockListener).onPreparedForReboot(eq(true));
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
assertFalse(mStorage.hasRebootEscrowServerBlob());
}
@@ -366,7 +381,7 @@
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
mService.setRebootEscrowListener(mockListener);
mService.prepareRebootEscrow();
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
clearInvocations(mRebootEscrow);
@@ -390,7 +405,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -414,7 +429,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -435,7 +450,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -453,10 +468,9 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
- mService.callToRebootEscrowIfNeeded(SECURE_SECONDARY_USER_ID, FAKE_SP_VERSION,
- FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
verify(mRebootEscrow, never()).storeKey(any());
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
@@ -488,7 +502,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -511,7 +525,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -554,7 +568,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -598,7 +612,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -643,7 +657,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -689,7 +703,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -738,7 +752,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -791,7 +805,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -846,7 +860,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -893,7 +907,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -949,7 +963,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -1008,7 +1022,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -1068,7 +1082,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -1124,7 +1138,7 @@
mService.prepareRebootEscrow();
clearInvocations(mServiceConnection);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
@@ -1176,7 +1190,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -1207,7 +1221,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -1235,7 +1249,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -1274,7 +1288,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
@@ -1309,7 +1323,7 @@
mService.prepareRebootEscrow();
clearInvocations(mRebootEscrow);
- mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
verify(mockListener).onPreparedForReboot(eq(true));
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
verify(mRebootEscrow, never()).storeKey(any());
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7125796..7e40f96 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -20,6 +20,8 @@
import static android.content.Context.SENSOR_SERVICE;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
@@ -327,7 +329,8 @@
+ " <name>THERMAL_TEST</name>\n"
+ " <flags>\n"
+ " <flag>FLAG_EMULATED_ONLY</flag>\n"
- + " <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+ + " <flag>FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+ + " <flag>FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE</flag>\n"
+ " </flags>\n"
+ " </device-state>\n"
+ "</device-state-config>\n";
@@ -354,7 +357,8 @@
new DeviceState(3, "OPENED", 0 /* flags */),
new DeviceState(4, "THERMAL_TEST",
DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
@@ -419,7 +423,8 @@
new DeviceState(3, "OPENED", 0 /* flags */),
new DeviceState(4, "THERMAL_TEST",
DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
mDeviceStateArrayCaptor.getValue());
Mockito.clearInvocations(listener);
@@ -451,7 +456,65 @@
new DeviceState(3, "OPENED", 0 /* flags */),
new DeviceState(4, "THERMAL_TEST",
DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ mDeviceStateArrayCaptor.getValue());
+ }
+
+ @Test
+ public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be disabled due to power save being enabled.
+ provider.onPowerSaveModeChanged(true /* isPowerSaveModeEnabled */);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be re-enabled.
+ provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
mDeviceStateArrayCaptor.getValue());
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 6edef75..07b4345 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -34,6 +34,7 @@
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.CombinedVibration;
import android.os.Handler;
import android.os.Process;
@@ -82,8 +83,8 @@
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
@@ -314,7 +315,7 @@
deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
}
// Force initialization of mIInputDevicesChangedListener, if it still haven't
- InputManager.getInstance().getInputDeviceIds();
+ InputManagerGlobal.getInstance().getInputDeviceIds();
mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
// Makes sure all callbacks from InputDeviceDelegate are executed.
mTestLooper.dispatchAll();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
new file mode 100644
index 0000000..b94ed01
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
+import android.net.Uri;
+import android.os.IInterface;
+import android.service.notification.Condition;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ConditionProvidersTest extends UiServiceTestCase {
+
+ private ConditionProviders mProviders;
+
+ @Mock
+ private IPackageManager mIpm;
+ @Mock
+ private ManagedServices.UserProfiles mUserProfiles;
+ @Mock
+ private ConditionProviders.Callback mCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
+ mProviders.setCallback(mCallback);
+ }
+
+ @Test
+ public void notifyConditions_findCondition() {
+ ComponentName cn = new ComponentName("package", "cls");
+ ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+ mock(IInterface.class), cn, 0, false, mock(ServiceConnection.class), 33, 100);
+ Condition[] conditions = new Condition[] {
+ new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+ new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE)
+ };
+
+ mProviders.notifyConditions("package", msi, conditions);
+
+ assertThat(mProviders.findCondition(cn, Uri.parse("a"))).isEqualTo(conditions[0]);
+ assertThat(mProviders.findCondition(cn, Uri.parse("b"))).isEqualTo(conditions[1]);
+ assertThat(mProviders.findCondition(null, Uri.parse("a"))).isNull();
+ assertThat(mProviders.findCondition(cn, null)).isNull();
+ }
+
+ @Test
+ public void notifyConditions_callbackOnConditionChanged() {
+ ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+ mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+ mock(ServiceConnection.class), 33, 100);
+ Condition[] conditionsToNotify = new Condition[] {
+ new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+ new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+ new Condition(Uri.parse("c"), "summary3", Condition.STATE_TRUE)
+ };
+
+ mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]));
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ @Test
+ public void notifyConditions_duplicateIds_ignored() {
+ ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+ mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+ mock(ServiceConnection.class), 33, 100);
+ Condition[] conditionsToNotify = new Condition[] {
+ new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+ new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+ new Condition(Uri.parse("a"), "summary3", Condition.STATE_FALSE),
+ new Condition(Uri.parse("a"), "summary4", Condition.STATE_FALSE)
+ };
+
+ mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ @Test
+ public void notifyConditions_nullItems_ignored() {
+ ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+ mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+ mock(ServiceConnection.class), 33, 100);
+ Condition[] conditionsToNotify = new Condition[] {
+ new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+ null,
+ null,
+ new Condition(Uri.parse("b"), "summary", Condition.STATE_TRUE)
+ };
+
+ mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
+ verifyNoMoreInteractions(mCallback);
+ }
+}
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 41a9504..5cbd120 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -10441,6 +10441,31 @@
}
@Test
+ public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
+ throws Exception {
+ // Given: a call notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: ALLOW_DISMISS_ONGOING is on
+ mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
+ when(mTelecomManager.isInManagedCall()).thenReturn(true);
+
+ Person person = new Person.Builder()
+ .setName("caller")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .setStyle(Notification.CallStyle.forOngoingCall(
+ person, mock(PendingIntent.class)))
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+ }
+
+
+ @Test
public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
// Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
// feature flag: ALLOW_DISMISS_ONGOING is on
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index c73237e..0ae579b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -376,46 +376,6 @@
}
@Test
- public void testGetAnimationTargets_windowsAreBeingReplaced() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
- // +- [AppWindow1] (being-replaced)
- // +- [Task2] - [ActivityRecord2] (closing, invisible)
- // +- [AppWindow2] (being-replaced)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrs.setTitle("AppWindow1");
- final TestWindowState appWindow1 = createWindowState(attrs, activity1);
- appWindow1.mWillReplaceWindow = true;
- activity1.addWindow(appWindow1);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
- attrs.setTitle("AppWindow2");
- final TestWindowState appWindow2 = createWindowState(attrs, activity2);
- appWindow2.mWillReplaceWindow = true;
- activity2.addWindow(appWindow2);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Animate opening apps even if it's already visible in case its windows are being replaced.
- // Don't animate closing apps if it's already invisible even though its windows are being
- // replaced.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
public void testGetAnimationTargets_openingClosingInDifferentTask() {
// [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
// | +- [ActivityRecord2] (invisible)
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 06b6ed8..da078a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -459,8 +459,17 @@
mainWindow.mInvGlobalScale = invGlobalScale;
mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+ doReturn(true).when(mActivity).isInLetterboxAnimation();
assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+ doReturn(false).when(mActivity).isInLetterboxAnimation();
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ doReturn(false).when(mainWindow).isOnScreen();
+ assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
+
+ doReturn(true).when(mActivity).isInLetterboxAnimation();
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
}
@Test
@@ -495,6 +504,7 @@
insets.addSource(taskbar);
}
doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+ doReturn(false).when(mActivity).isInLetterboxAnimation();
doReturn(true).when(mActivity).isVisible();
doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
doReturn(insets).when(mainWindow).getInsetsState();
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 753cc62..2cc46a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -51,6 +51,7 @@
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
@@ -3918,6 +3919,24 @@
assertTrue(mActivity.inSizeCompatMode());
}
+ @Test
+ public void testTopActivityInSizeCompatMode_pausedAndInSizeCompatMode_returnsTrue() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ spyOn(mActivity);
+ doReturn(mTask).when(mActivity).getOrganizedTask();
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ mActivity.setState(PAUSED, "test");
+
+ assertTrue(mActivity.inSizeCompatMode());
+ assertEquals(mActivity.getState(), PAUSED);
+ assertTrue(mActivity.isVisible());
+ assertTrue(mTask.getTaskInfo().topActivityInSizeCompat);
+ }
+
/**
* Tests that all three paths in which aspect ratio logic can be applied yield the same
* result, which is that aspect ratio is respected on app bounds. The three paths are
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index debfc84..616d528 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -40,6 +40,7 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -54,6 +55,7 @@
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -520,6 +522,47 @@
}
@Test
+ public void testCreateInfo_MultiDisplay() {
+ DisplayContent otherDisplay = createNewDisplay();
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task display0Task = createTask(mDisplayContent);
+ final Task display1Task = createTask(otherDisplay);
+ // Start states.
+ changes.put(display0Task,
+ new Transition.ChangeInfo(display0Task, false /* vis */, true /* exChg */));
+ changes.put(display1Task,
+ new Transition.ChangeInfo(display1Task, false /* vis */, true /* exChg */));
+ fillChangeMap(changes, display0Task);
+ fillChangeMap(changes, display1Task);
+ // End states.
+ display0Task.setVisibleRequested(true);
+ display1Task.setVisibleRequested(true);
+
+ final int transit = transition.mType;
+ int flags = 0;
+
+ participants.add(display0Task);
+ participants.add(display1Task);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+ assertEquals(2, info.getRootCount());
+ // Check that the changes are assigned to the correct display
+ assertEquals(mDisplayContent.getDisplayId(), info.getChange(
+ display0Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+ assertEquals(otherDisplay.getDisplayId(), info.getChange(
+ display1Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+ // Check that roots can be found by display and have the correct display
+ assertEquals(mDisplayContent.getDisplayId(),
+ info.getRoot(info.findRootIndex(mDisplayContent.getDisplayId())).getDisplayId());
+ assertEquals(otherDisplay.getDisplayId(),
+ info.getRoot(info.findRootIndex(otherDisplay.getDisplayId())).getDisplayId());
+ }
+
+ @Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
@@ -1350,7 +1393,7 @@
verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
- openTransition.finishTransition();
+ controller.finishTransition(openTransition);
// We are now going to simulate closing task1 to return back to (open) task2.
final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
@@ -1359,7 +1402,13 @@
closeTransition.collectExistenceChange(activity1);
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
- closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
+ closeTransition.setTransientLaunch(activity2, task1);
+ final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
+ assertNotNull(task1ChangeInfo);
+ assertTrue(task1ChangeInfo.hasChanged());
+ final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
+ assertNotNull(activity1ChangeInfo);
+ assertTrue(activity1ChangeInfo.hasChanged());
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
@@ -1375,9 +1424,27 @@
verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
enteringAnimReports.clear();
- closeTransition.finishTransition();
+ doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
+ anyInt(), anyBoolean(), anyBoolean());
+ final boolean[] wasInFinishingTransition = { false };
+ controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forToken(token);
+ if (r != null) {
+ wasInFinishingTransition[0] = controller.inFinishingTransition(r);
+ }
+ }
+ });
+ controller.finishTransition(closeTransition);
+ assertTrue(wasInFinishingTransition[0]);
+ assertNull(controller.mFinishingTransition);
+ assertTrue(activity2.isVisible());
assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
+ // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
+ assertFalse(activity1.isVisibleRequested());
+ assertFalse(activity1.isVisible());
assertFalse(activity1.app.hasActivityInVisibleTask());
verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 65631ea..984b868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -396,7 +396,7 @@
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
token.finishSync(t, false /* cancel */);
transit.onTransactionReady(transit.getSyncId(), t);
- dc.mTransitionController.finishTransition(transit.getToken());
+ dc.mTransitionController.finishTransition(transit);
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index a68a573..17ad4e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1486,9 +1486,9 @@
assertEquals(rootTask.mTaskId, info.taskId);
assertTrue(info.topActivityInSizeCompat);
- // Ensure task info show top activity that is not in foreground as not in size compat.
+ // Ensure task info show top activity that is not visible as not in size compat.
clearInvocations(organizer);
- doReturn(false).when(activity).isState(RESUMED);
+ doReturn(false).when(activity).isVisible();
rootTask.onSizeCompatActivityChanged();
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
@@ -1498,7 +1498,7 @@
// Ensure task info show non size compat top activity as not in size compat.
clearInvocations(organizer);
- doReturn(true).when(activity).isState(RESUMED);
+ doReturn(true).when(activity).isVisible();
doReturn(false).when(activity).inSizeCompatMode();
rootTask.onSizeCompatActivityChanged();
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
index c2ee079..2a3c9bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
@@ -129,4 +131,14 @@
assertEquals(uid2processes.size(), 1);
assertEquals(mProcessMap.getProcess(FAKE_PID1), pid1uid2);
}
+
+ @Test
+ public void testRemove_callsDestroy() {
+ var proc = spy(pid1uid1);
+ mProcessMap.put(FAKE_PID1, proc);
+
+ mProcessMap.remove(FAKE_PID1);
+
+ verify(proc).destroy();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d6cfd00..cf83981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -159,6 +159,17 @@
}
@Test
+ public void testDestroy_unregistersDisplayAreaListener() {
+ final TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+ final DisplayArea imeContainer1 = testDisplayContent1.getImeContainer();
+ mWpc.registerDisplayAreaConfigurationListener(imeContainer1);
+
+ mWpc.destroy();
+
+ assertNull(mWpc.getDisplayArea());
+ }
+
+ @Test
public void testSetRunningRecentsAnimation() {
mWpc.setRunningRecentsAnimation(true);
mWpc.setRunningRecentsAnimation(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ce6cd90..b80500a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1799,7 +1799,7 @@
}
public void finish() {
- mController.finishTransition(mLastTransit.getToken());
+ mController.finishTransition(mLastTransit);
}
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 2d382a2..79046db 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -37,6 +37,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -160,11 +161,20 @@
return null;
}
final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
- if (!isServiceAvailableForUser(serviceComponent)) {
+ boolean isServiceAvailableForUser;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ isServiceAvailableForUser = isServiceAvailableForUser(serviceComponent);
if (mMaster.verbose) {
- Slog.v(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
- + " is not available,");
+ Slog.v(TAG, "ensureRemoteServiceLocked(): isServiceAvailableForUser="
+ + isServiceAvailableForUser);
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (!isServiceAvailableForUser) {
+ Slog.w(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
+ + " is not available,");
return null;
}
mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent,
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 6138f67..943d8d6 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -87,7 +87,7 @@
* must call {@link #destroy()} to signal to the framework that the {@code Connection} is no
* longer used and associated resources may be recovered.
* <p>
- * Subclasses of {@code Connection} override the {@code on*} methods to provide the the
+ * Subclasses of {@code Connection} override the {@code on*} methods to provide the
* {@link ConnectionService}'s implementation of calling functionality. The {@code on*} methods are
* called by Telecom to inform an instance of a {@code Connection} of actions specific to that
* {@code Connection} instance.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bf12b9c..212dc41 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1628,6 +1628,7 @@
* <li> 9: WiFi Calling</li>
* <li> 10: VoWifi</li>
* <li> 11: %s WiFi Calling</li>
+ * <li> 12: WiFi Call</li>
* @hide
*/
public static final String KEY_WFC_SPN_FORMAT_IDX_INT = "wfc_spn_format_idx_int";
@@ -1974,8 +1975,13 @@
/**
* Boolean indicating if LTE+ icon should be shown if available.
*/
- public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
- "hide_lte_plus_data_icon_bool";
+ public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
+
+ /**
+ * Boolean indicting if the 5G slice icon should be shown if available.
+ * @hide
+ */
+ public static final String KEY_SHOW_5G_SLICE_ICON_BOOL = "show_5g_slice_icon_bool";
/**
* The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
@@ -9913,6 +9919,7 @@
sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
+ sDefaults.putBoolean(KEY_SHOW_5G_SLICE_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index dd08085..1668193 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -102,12 +102,11 @@
QosBearerSession other = (QosBearerSession) o;
return this.qosBearerSessionId == other.qosBearerSessionId
- && this.qos.equals(other.qos)
+ && Objects.equals(this.qos, other.qos)
&& this.qosBearerFilterList.size() == other.qosBearerFilterList.size()
&& this.qosBearerFilterList.containsAll(other.qosBearerFilterList);
}
-
public static final @NonNull Parcelable.Creator<QosBearerSession> CREATOR =
new Parcelable.Creator<QosBearerSession>() {
@Override
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
index 98221c9..cd9d81e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
@@ -22,13 +22,6 @@
*/
oneway interface ISatelliteStateCallback {
/**
- * Indicates that the satellite has pending datagrams for the device to be pulled.
- *
- * @param count Number of pending datagrams.
- */
- void onPendingDatagramCount(in int count);
-
- /**
* Indicates that the satellite modem state has changed.
*
* @param state The current satellite modem state.
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
similarity index 67%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index d3f1091..a81444d 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -22,17 +22,24 @@
* Interface for position update and datagram transfer state change callback.
* @hide
*/
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteTransmissionUpdateCallback {
/**
- * Called when satellite datagram transfer state changed.
+ * Called when satellite datagram send state changed.
*
- * @param state The new datagram transfer state.
+ * @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
- * @param receivePendingCount The number of datagrams that are currently being received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
- in int receivePendingCount, in int errorCode);
+ void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+
+ /**
+ * Called when satellite datagram receive state changed.
+ *
+ * @param state The new receive datagram transfer state.
+ * @param receivePendingCount The number of datagrams that are currently pending to be received.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ */
+ void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
/**
* Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index a3c3f19..7c794473 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -30,31 +30,12 @@
/** Satellite elevation in degrees */
private float mSatelliteElevationDegrees;
- /** Antenna azimuth in degrees */
- private float mAntennaAzimuthDegrees;
-
- /**
- * Angle of rotation about the x axis. This value represents the angle between a plane
- * parallel to the device's screen and a plane parallel to the ground.
- */
- private float mAntennaPitchDegrees;
-
- /**
- * Angle of rotation about the y axis. This value represents the angle between a plane
- * perpendicular to the device's screen and a plane parallel to the ground.
- */
- private float mAntennaRollDegrees;
-
/**
* @hide
*/
- public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees,
- float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+ public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
mSatelliteElevationDegrees = satelliteElevationDegrees;
- mAntennaAzimuthDegrees = antennaAzimuthDegrees;
- mAntennaPitchDegrees = antennaPitchDegrees;
- mAntennaRollDegrees = antennaRollDegrees;
}
private PointingInfo(Parcel in) {
@@ -70,9 +51,6 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeFloat(mSatelliteAzimuthDegrees);
out.writeFloat(mSatelliteElevationDegrees);
- out.writeFloat(mAntennaAzimuthDegrees);
- out.writeFloat(mAntennaPitchDegrees);
- out.writeFloat(mAntennaRollDegrees);
}
public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
@@ -99,18 +77,6 @@
sb.append("SatelliteElevationDegrees:");
sb.append(mSatelliteElevationDegrees);
- sb.append(",");
-
- sb.append("AntennaAzimuthDegrees:");
- sb.append(mAntennaAzimuthDegrees);
- sb.append(",");
-
- sb.append("AntennaPitchDegrees:");
- sb.append(mAntennaPitchDegrees);
- sb.append(",");
-
- sb.append("AntennaRollDegrees:");
- sb.append(mAntennaRollDegrees);
return sb.toString();
}
@@ -122,23 +88,8 @@
return mSatelliteElevationDegrees;
}
- public float getAntennaAzimuthDegrees() {
- return mAntennaAzimuthDegrees;
- }
-
- public float getAntennaPitchDegrees() {
- return mAntennaPitchDegrees;
- }
-
- public float getAntennaRollDegrees() {
- return mAntennaRollDegrees;
- }
-
private void readFromParcel(Parcel in) {
mSatelliteAzimuthDegrees = in.readFloat();
mSatelliteElevationDegrees = in.readFloat();
- mAntennaAzimuthDegrees = in.readFloat();
- mAntennaPitchDegrees = in.readFloat();
- mAntennaRollDegrees = in.readFloat();
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 889856b..df80159 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -33,31 +33,24 @@
@NonNull @SatelliteManager.NTRadioTechnology private Set<Integer> mSupportedRadioTechnologies;
/**
- * Whether satellite modem is always on.
- * This indicates the power impact of keeping it on is very minimal.
- */
- private boolean mIsAlwaysOn;
-
- /**
* Whether UE needs to point to a satellite to send and receive data.
*/
- private boolean mNeedsPointingToSatellite;
+ private boolean mIsPointingRequired;
/**
- * Whether UE needs a separate SIM profile to communicate with the satellite network.
+ * The maximum number of bytes per datagram that can be sent over satellite.
*/
- private boolean mNeedsSeparateSimProfile;
+ private int mMaxBytesPerOutgoingDatagram;
/**
* @hide
*/
- public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
- boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
+ public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
+ boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
mSupportedRadioTechnologies = supportedRadioTechnologies == null
? new HashSet<>() : supportedRadioTechnologies;
- mIsAlwaysOn = isAlwaysOn;
- mNeedsPointingToSatellite = needsPointingToSatellite;
- mNeedsSeparateSimProfile = needsSeparateSimProfile;
+ mIsPointingRequired = isPointingRequired;
+ mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
}
private SatelliteCapabilities(Parcel in) {
@@ -80,9 +73,8 @@
out.writeInt(0);
}
- out.writeBoolean(mIsAlwaysOn);
- out.writeBoolean(mNeedsPointingToSatellite);
- out.writeBoolean(mNeedsSeparateSimProfile);
+ out.writeBoolean(mIsPointingRequired);
+ out.writeInt(mMaxBytesPerOutgoingDatagram);
}
@NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -111,16 +103,12 @@
sb.append("none,");
}
- sb.append("isAlwaysOn:");
- sb.append(mIsAlwaysOn);
+ sb.append("isPointingRequired:");
+ sb.append(mIsPointingRequired);
sb.append(",");
- sb.append("needsPointingToSatellite:");
- sb.append(mNeedsPointingToSatellite);
- sb.append(",");
-
- sb.append("needsSeparateSimProfile:");
- sb.append(mNeedsSeparateSimProfile);
+ sb.append("maxBytesPerOutgoingDatagram");
+ sb.append(mMaxBytesPerOutgoingDatagram);
return sb.toString();
}
@@ -133,33 +121,22 @@
}
/**
- * Get whether the satellite modem is always on.
- * This indicates the power impact of keeping it on is very minimal.
- *
- * @return {@code true} if the satellite modem is always on and {@code false} otherwise.
- */
- public boolean isAlwaysOn() {
- return mIsAlwaysOn;
- }
-
- /**
* Get whether UE needs to point to a satellite to send and receive data.
*
- * @return {@code true} if UE needs to pointing to a satellite to send and receive data and
+ * @return {@code true} if UE needs to point to a satellite to send and receive data and
* {@code false} otherwise.
*/
- public boolean needsPointingToSatellite() {
- return mNeedsPointingToSatellite;
+ public boolean isPointingRequired() {
+ return mIsPointingRequired;
}
/**
- * Get whether UE needs a separate SIM profile to communicate with the satellite network.
+ * The maximum number of bytes per datagram that can be sent over satellite.
*
- * @return {@code true} if UE needs a separate SIM profile to comunicate with the satellite
- * network and {@code false} otherwise.
+ * @return The maximum number of bytes per datagram that can be sent over satellite.
*/
- public boolean needsSeparateSimProfile() {
- return mNeedsSeparateSimProfile;
+ public int getMaxBytesPerOutgoingDatagram() {
+ return mMaxBytesPerOutgoingDatagram;
}
private void readFromParcel(Parcel in) {
@@ -171,8 +148,7 @@
}
}
- mIsAlwaysOn = in.readBoolean();
- mNeedsPointingToSatellite = in.readBoolean();
- mNeedsSeparateSimProfile = in.readBoolean();
+ mIsPointingRequired = in.readBoolean();
+ mMaxBytesPerOutgoingDatagram = in.readInt();
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index cc5a9f4..d3cb8a0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,7 +27,7 @@
/**
* Datagram to be sent or received over satellite.
*/
- private byte[] mData;
+ @NonNull private byte[] mData;
/**
* @hide
@@ -51,8 +50,8 @@
out.writeByteArray(mData);
}
- public static final @android.annotation.NonNull Creator<SatelliteDatagram> CREATOR =
- new Creator<SatelliteDatagram>() {
+ @NonNull public static final Creator<SatelliteDatagram> CREATOR =
+ new Creator<>() {
@Override
public SatelliteDatagram createFromParcel(Parcel in) {
return new SatelliteDatagram(in);
@@ -64,8 +63,7 @@
}
};
- @Nullable
- public byte[] getSatelliteDatagram() {
+ @NonNull public byte[] getSatelliteDatagram() {
return mData;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 213b985..f237ada 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,48 +17,18 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.os.Binder;
import com.android.internal.telephony.ILongConsumer;
-import java.util.concurrent.Executor;
-
/**
* A callback class for listening to satellite datagrams.
*
* @hide
*/
-public class SatelliteDatagramCallback {
- private final CallbackBinder mBinder = new CallbackBinder(this);
-
- private static class CallbackBinder extends ISatelliteDatagramCallback.Stub {
- private final SatelliteDatagramCallback mLocalCallback;
- private Executor mExecutor;
-
- private CallbackBinder(SatelliteDatagramCallback localCallback) {
- mLocalCallback = localCallback;
- }
-
- @Override
- public void onSatelliteDatagramReceived(long datagramId,
- @NonNull SatelliteDatagram datagram, int pendingCount,
- @NonNull ILongConsumer callback) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
- datagram, pendingCount, callback));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- private void setExecutor(Executor executor) {
- mExecutor = executor;
- }
- }
-
+public interface SatelliteDatagramCallback {
/**
* Called when there is an incoming datagram to be received.
+ *
* @param datagramId An id that uniquely identifies incoming datagram.
* @param datagram Datagram to be received over satellite.
* @param pendingCount Number of datagrams yet to be received by the app.
@@ -66,19 +36,6 @@
* datagramId to Telephony. If the callback is not received within five minutes,
* Telephony will resend the datagram.
*/
- public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
- int pendingCount, @NonNull ILongConsumer callback) {
- // Base Implementation
- }
-
- /** @hide */
- @NonNull
- final ISatelliteDatagramCallback getBinder() {
- return mBinder;
- }
-
- /** @hide */
- public void setExecutor(@NonNull Executor executor) {
- mBinder.setExecutor(executor);
- }
+ void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+ int pendingCount, @NonNull ILongConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 9ec6929..e32566d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -36,12 +36,15 @@
import android.telephony.TelephonyFrameworkInitializer;
import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ILongConsumer;
import com.android.internal.telephony.ITelephony;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -55,6 +58,17 @@
public class SatelliteManager {
private static final String TAG = "SatelliteManager";
+ private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback>
+ sSatelliteDatagramCallbackMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
+ ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
+ new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
+ sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
+ ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
+ new ConcurrentHashMap<>();
+
private final int mSubId;
/**
@@ -66,6 +80,7 @@
* Create an instance of the SatelliteManager.
*
* @param context The context the SatelliteManager belongs to.
+ * @hide
*/
public SatelliteManager(@Nullable Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -116,7 +131,7 @@
/**
* Bundle key to get the response from
- * {@link #requestIsSatelliteDemoModeEnabled(Executor, OutcomeReceiver)}.
+ * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
* @hide
*/
public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
@@ -137,14 +152,6 @@
/**
* Bundle key to get the response from
- * {@link #requestMaxSizePerSendingDatagram(Executor, OutcomeReceiver)} .
- * @hide
- */
- public static final String KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT =
- "max_characters_per_satellite_text";
-
- /**
- * Bundle key to get the response from
* {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
* @hide
*/
@@ -319,23 +326,25 @@
public @interface NTRadioTechnology {}
/**
- * Request to enable or disable the satellite modem. If the satellite modem is enabled, this
- * will also disable the cellular modem, and if the satellite modem is disabled, this will also
- * re-enable the cellular modem.
+ * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+ * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+ * this may also re-enable the cellular modem.
*
- * @param enable {@code true} to enable the satellite modem and {@code false} to disable.
+ * @param enableSatellite {@code true} to enable the satellite modem and
+ * {@code false} to disable.
+ * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
* @param executor The executor on which the error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestSatelliteEnabled(
- boolean enable, @NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener) {
+ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+ @NonNull @CallbackExecutor Executor executor,
+ @SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
+ Objects.requireNonNull(resultListener);
try {
ITelephony telephony = getITelephony();
@@ -344,10 +353,11 @@
@Override
public void accept(int result) {
executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
+ () -> resultListener.accept(result)));
}
};
- telephony.requestSatelliteEnabled(mSubId, enable, errorCallback);
+ telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
+ errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -412,50 +422,13 @@
}
/**
- * Request to enable or disable the satellite service demo mode.
- *
- * @param enable {@code true} to enable the satellite demo mode and {@code false} to disable.
- * @param executor The executor on which the error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
- *
- * @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
- */
- @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestSatelliteDemoModeEnabled(boolean enable,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener) {
- Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
-
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null) {
- IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
- @Override
- public void accept(int result) {
- executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
- }
- };
- telephony.requestSatelliteDemoModeEnabled(mSubId, enable, errorCallback);
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
- } catch (RemoteException ex) {
- Rlog.e(TAG, "requestSatelliteDemoModeEnabled() RemoteException: ", ex);
- ex.rethrowFromSystemServer();
- }
- }
-
- /**
* Request to get whether the satellite service demo mode is enabled.
*
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
* If the request is successful, {@link OutcomeReceiver#onResult(Object)}
- * will return a {@code boolean} with value {@code true} if the satellite
- * demo mode is enabled and {@code false} otherwise.
+ * will return a {@code boolean} with value {@code true} if demo mode is enabled
+ * and {@code false} otherwise.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
* will return a {@link SatelliteException} with the {@link SatelliteError}.
*
@@ -463,7 +436,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestIsSatelliteDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
+ public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -492,12 +465,12 @@
}
}
};
- telephony.requestIsSatelliteDemoModeEnabled(mSubId, receiver);
+ telephony.requestIsDemoModeEnabled(mSubId, receiver);
} else {
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("requestIsSatelliteDemoModeEnabled() RemoteException: " + ex);
+ loge("requestIsDemoModeEnabled() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
}
@@ -674,6 +647,7 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteDatagramTransferState {}
+ // TODO: Split into two enums for sending and receiving states
/**
* Satellite modem is in idle state.
@@ -742,145 +716,125 @@
public @interface DatagramType {}
/**
- * Start receiving satellite position updates.
+ * Start receiving satellite transmission updates.
* This can be called by the pointing UI when the user starts pointing to the satellite.
* Modem should continue to report the pointing input as the device or satellite moves.
- * Satellite position updates are started only on {@link #SATELLITE_ERROR_NONE}.
+ * Satellite transmission updates are started only on {@link #SATELLITE_ERROR_NONE}.
* All other results indicate that this operation failed.
- * Once satellite position updates begin, datagram transfer state updates will be sent
- * through {@link SatellitePositionUpdateCallback}.
+ * Once satellite transmission updates begin, position and datagram transfer state updates
+ * will be sent through {@link SatelliteTransmissionUpdateCallback}.
*
* @param executor The executor on which the callback and error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
- * @param callback The callback to notify of changes in satellite position.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+ * @param callback The callback to notify of satellite transmission updates.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void startSatellitePositionUpdates(@NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener,
- @NonNull SatellitePositionUpdateCallback callback) {
+ public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
+ @SatelliteError @NonNull Consumer<Integer> resultListener,
+ @NonNull SatelliteTransmissionUpdateCallback callback) {
Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
+ Objects.requireNonNull(resultListener);
Objects.requireNonNull(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- callback.setExecutor(executor);
IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
@Override
public void accept(int result) {
executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
+ () -> resultListener.accept(result)));
}
};
- telephony.startSatellitePositionUpdates(
- mSubId, errorCallback, callback.getBinder());
+ ISatelliteTransmissionUpdateCallback internalCallback =
+ new ISatelliteTransmissionUpdateCallback.Stub() {
+
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatellitePositionChanged(pointingInfo)));
+ }
+
+ @Override
+ public void onSendDatagramStateChanged(int state, int sendPendingCount,
+ int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSendDatagramStateChanged(
+ state, sendPendingCount, errorCode)));
+ }
+
+ @Override
+ public void onReceiveDatagramStateChanged(int state,
+ int receivePendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onReceiveDatagramStateChanged(
+ state, receivePendingCount, errorCode)));
+ }
+ };
+ sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
+ telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
+ internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("startSatellitePositionUpdates() RemoteException: " + ex);
+ loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
}
/**
- * Stop receiving satellite position updates.
+ * Stop receiving satellite transmission updates.
* This can be called by the pointing UI when the user stops pointing to the satellite.
- * Satellite position updates are stopped and the callback is unregistered only on
+ * Satellite transmission updates are stopped and the callback is unregistered only on
* {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
*
- * @param callback The callback that was passed to
- * {@link #startSatellitePositionUpdates(Executor, Consumer, SatellitePositionUpdateCallback)}.
+ * @param callback The callback that was passed to {@link
+ * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
* @param executor The executor on which the error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void stopSatellitePositionUpdates(@NonNull SatellitePositionUpdateCallback callback,
+ public void stopSatelliteTransmissionUpdates(
+ @NonNull SatelliteTransmissionUpdateCallback callback,
@NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<Integer> errorCodeListener) {
+ @SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(callback);
Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
+ Objects.requireNonNull(resultListener);
+ ISatelliteTransmissionUpdateCallback internalCallback =
+ sSatelliteTransmissionUpdateCallbackMap.remove(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
- @Override
- public void accept(int result) {
- executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
- }
- };
- telephony.stopSatellitePositionUpdates(mSubId, errorCallback,
- callback.getBinder());
- // TODO: Notify SmsHandler that pointing UI stopped
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
- } catch (RemoteException ex) {
- loge("stopSatellitePositionUpdates() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
- }
- }
-
- /**
- * Request to get the maximum number of bytes per datagram that can be sent to satellite.
- *
- * @param executor The executor on which the callback will be called.
- * @param callback The callback object to which the result will be delivered.
- * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
- * will return the maximum number of bytes per datagram that can be sent to
- * satellite.
- * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
- *
- * @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
- */
- @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestMaxSizePerSendingDatagram(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
- Objects.requireNonNull(executor);
- Objects.requireNonNull(callback);
-
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null) {
- ResultReceiver receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == SATELLITE_ERROR_NONE) {
- if (resultData.containsKey(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT)) {
- int maxCharacters =
- resultData.getInt(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT);
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(maxCharacters)));
- } else {
- loge("KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT does not exist.");
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(
- new SatelliteException(SATELLITE_REQUEST_FAILED))));
- }
- } else {
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(new SatelliteException(resultCode))));
+ if (internalCallback != null) {
+ IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
}
- }
- };
- telephony.requestMaxSizePerSendingDatagram(mSubId, receiver);
+ };
+ telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
+ internalCallback);
+ // TODO: Notify SmsHandler that pointing UI stopped
+ } else {
+ loge("stopSatelliteTransmissionUpdates: No internal callback.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_INVALID_ARGUMENTS)));
+ }
} else {
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("requestMaxCharactersPerSatelliteTextMessage() RemoteException: " + ex);
+ loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
}
@@ -891,23 +845,24 @@
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
+ * @param regionId The region ID for the device's current location.
* @param cancellationSignal The optional signal used by the caller to cancel the provision
* request. Even when the cancellation is signaled, Telephony will
* still trigger the callback to return the result of this request.
* @param executor The executor on which the error code listener will be called.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void provisionSatelliteService(@NonNull String token,
+ public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor executor,
- @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+ @SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(token);
Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
+ Objects.requireNonNull(resultListener);
ICancellationSignal cancelRemote = null;
try {
@@ -917,10 +872,11 @@
@Override
public void accept(int result) {
executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
+ () -> resultListener.accept(result)));
}
};
- cancelRemote = telephony.provisionSatelliteService(mSubId, token, errorCallback);
+ cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+ errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -942,7 +898,7 @@
* {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
*
* @param token The token of the device/subscription to be deprovisioned.
- * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
@@ -950,10 +906,10 @@
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void deprovisionSatelliteService(@NonNull String token,
@NonNull @CallbackExecutor Executor executor,
- @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+ @SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(token);
Objects.requireNonNull(executor);
- Objects.requireNonNull(errorCodeListener);
+ Objects.requireNonNull(resultListener);
try {
ITelephony telephony = getITelephony();
@@ -962,7 +918,7 @@
@Override
public void accept(int result) {
executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
+ () -> resultListener.accept(result)));
}
};
telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
@@ -996,9 +952,18 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- callback.setExecutor(executor);
+ ISatelliteProvisionStateCallback internalCallback =
+ new ISatelliteProvisionStateCallback.Stub() {
+ @Override
+ public void onSatelliteProvisionStateChanged(boolean provisioned) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteProvisionStateChanged(
+ provisioned)));
+ }
+ };
+ sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteProvisionStateChanged(
- mSubId, callback.getBinder());
+ mSubId, internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1023,11 +988,17 @@
public void unregisterForSatelliteProvisionStateChanged(
@NonNull SatelliteProvisionStateCallback callback) {
Objects.requireNonNull(callback);
+ ISatelliteProvisionStateCallback internalCallback =
+ sSatelliteProvisionStateCallbackMap.remove(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.unregisterForSatelliteProvisionStateChanged(mSubId, callback.getBinder());
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+ } else {
+ loge("unregisterForSatelliteProvisionStateChanged: No internal callback.");
+ }
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1112,9 +1083,15 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- callback.setExecutor(executor);
- return telephony.registerForSatelliteModemStateChanged(mSubId,
- callback.getBinder());
+ ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+ @Override
+ public void onSatelliteModemStateChanged(int state) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onSatelliteModemStateChanged(state)));
+ }
+ };
+ sSatelliteStateCallbackMap.put(callback, internalCallback);
+ return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1138,11 +1115,16 @@
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
Objects.requireNonNull(callback);
+ ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.unregisterForSatelliteModemStateChanged(mSubId, callback.getBinder());
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteModemStateChanged(mSubId, internalCallback);
+ } else {
+ loge("unregisterForSatelliteModemStateChanged: No internal callback.");
+ }
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1155,8 +1137,6 @@
/**
* Register to receive incoming datagrams over satellite.
*
- * @param datagramType datagram type indicating whether the datagram is of type
- * SOS_SMS or LOCATION_SHARING.
* @param executor The executor on which the callback will be called.
* @param callback The callback to handle incoming datagrams over satellite.
* This callback with be invoked when a new datagram is received from satellite.
@@ -1167,7 +1147,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @SatelliteError public int registerForSatelliteDatagram(@DatagramType int datagramType,
+ @SatelliteError public int registerForSatelliteDatagram(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteDatagramCallback callback) {
Objects.requireNonNull(executor);
@@ -1176,9 +1156,19 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- callback.setExecutor(executor);
- return telephony.registerForSatelliteDatagram(mSubId, datagramType,
- callback.getBinder());
+ ISatelliteDatagramCallback internalCallback =
+ new ISatelliteDatagramCallback.Stub() {
+ @Override
+ public void onSatelliteDatagramReceived(long datagramId,
+ @NonNull SatelliteDatagram datagram, int pendingCount,
+ @NonNull ILongConsumer ack) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteDatagramReceived(
+ datagramId, datagram, pendingCount, ack)));
+ }
+ };
+ sSatelliteDatagramCallbackMap.put(callback, internalCallback);
+ return telephony.registerForSatelliteDatagram(mSubId, internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1194,7 +1184,7 @@
* If callback was not registered before, the request will be ignored.
*
* @param callback The callback that was passed to
- * {@link #registerForSatelliteDatagram(int, Executor, SatelliteDatagramCallback)}.
+ * {@link #registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)}.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
@@ -1202,11 +1192,17 @@
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
Objects.requireNonNull(callback);
+ ISatelliteDatagramCallback internalCallback =
+ sSatelliteDatagramCallbackMap.remove(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.unregisterForSatelliteDatagram(mSubId, callback.getBinder());
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteDatagram(mSubId, internalCallback);
+ } else {
+ loge("unregisterForSatelliteDatagram: No internal callback.");
+ }
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1222,7 +1218,7 @@
* This method requests modem to check if there are any pending datagrams to be received over
* satellite. If there are any incoming datagrams, they will be received via
* {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
- * ISatelliteDatagramReceiverAck)}
+ * ILongConsumer)}
*
* @param executor The executor on which the result listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
@@ -1371,9 +1367,8 @@
}
/**
- * Request to get the time after which the satellite will be visible. This is an
- * {@code int} representing the duration in seconds after which the satellite will be visible.
- * This will return {@code 0} if the satellite is currently visible.
+ * Request to get the duration in seconds after which the satellite will be visible.
+ * This will be {@link Duration#ZERO} if the satellite is currently visible.
*
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
@@ -1387,7 +1382,7 @@
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
+ @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -1402,7 +1397,8 @@
int nextVisibilityDuration =
resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(nextVisibilityDuration)));
+ callback.onResult(
+ Duration.ofSeconds(nextVisibilityDuration))));
} else {
loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
deleted file mode 100644
index d44a84d..0000000
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.satellite;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
-/**
- * A callback class for monitoring satellite position update and datagram transfer state change
- * events.
- *
- * @hide
- */
-public class SatellitePositionUpdateCallback {
- private final CallbackBinder mBinder = new CallbackBinder(this);
-
- private static class CallbackBinder extends ISatellitePositionUpdateCallback.Stub {
- private final SatellitePositionUpdateCallback mLocalCallback;
- private Executor mExecutor;
-
- private CallbackBinder(SatellitePositionUpdateCallback localCallback) {
- mLocalCallback = localCallback;
- }
-
- @Override
- public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() ->
- mLocalCallback.onSatellitePositionChanged(pointingInfo));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- @Override
- public void onDatagramTransferStateChanged(
- @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
- int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() ->
- mLocalCallback.onDatagramTransferStateChanged(
- state, sendPendingCount, receivePendingCount, errorCode));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- private void setExecutor(Executor executor) {
- mExecutor = executor;
- }
- }
-
- /**
- * Called when the satellite position changed.
- *
- * @param pointingInfo The pointing info containing the satellite location.
- */
- public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
- // Base Implementation
- }
-
- /**
- * Called when satellite datagram transfer state changed.
- *
- * @param state The new datagram transfer state.
- * @param sendPendingCount The number of datagrams that are currently being sent.
- * @param receivePendingCount The number of datagrams that are currently being received.
- * @param errorCode If datagram transfer failed, the reason for failure.
- */
- public void onDatagramTransferStateChanged(
- @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
- int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
- // Base Implementation
- }
-
- /**@hide*/
- @NonNull
- final ISatellitePositionUpdateCallback getBinder() {
- return mBinder;
- }
-
- /**@hide*/
- public void setExecutor(@NonNull Executor executor) {
- mBinder.setExecutor(executor);
- }
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 2b6a5d9..a62eb8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,61 +16,17 @@
package android.telephony.satellite;
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
/**
* A callback class for monitoring satellite provision state change events.
*
* @hide
*/
-public class SatelliteProvisionStateCallback {
- private final CallbackBinder mBinder = new CallbackBinder(this);
-
- private static class CallbackBinder extends ISatelliteProvisionStateCallback.Stub {
- private final SatelliteProvisionStateCallback mLocalCallback;
- private Executor mExecutor;
-
- private CallbackBinder(SatelliteProvisionStateCallback localCallback) {
- mLocalCallback = localCallback;
- }
-
- @Override
- public void onSatelliteProvisionStateChanged(boolean provisioned) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() ->
- mLocalCallback.onSatelliteProvisionStateChanged(provisioned));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- private void setExecutor(Executor executor) {
- mExecutor = executor;
- }
- }
-
+public interface SatelliteProvisionStateCallback {
/**
* Called when satellite provision state changes.
*
* @param provisioned The new provision state. {@code true} means satellite is provisioned
* {@code false} means satellite is not provisioned.
*/
- public void onSatelliteProvisionStateChanged(boolean provisioned) {
- // Base Implementation
- }
-
- /**@hide*/
- @NonNull
- final ISatelliteProvisionStateCallback getBinder() {
- return mBinder;
- }
-
- /**@hide*/
- public void setExecutor(@NonNull Executor executor) {
- mBinder.setExecutor(executor);
- }
+ void onSatelliteProvisionStateChanged(boolean provisioned);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 17d05b7..d9ecaa3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,80 +16,15 @@
package android.telephony.satellite;
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
/**
* A callback class for monitoring satellite modem state change events.
*
* @hide
*/
-public class SatelliteStateCallback {
- private final CallbackBinder mBinder = new CallbackBinder(this);
-
- private static class CallbackBinder extends ISatelliteStateCallback.Stub {
- private final SatelliteStateCallback mLocalCallback;
- private Executor mExecutor;
-
- private CallbackBinder(SatelliteStateCallback localCallback) {
- mLocalCallback = localCallback;
- }
-
- @Override
- public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() ->
- mLocalCallback.onSatelliteModemStateChanged(state));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- @Override
- public void onPendingDatagramCount(int count) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() ->
- mLocalCallback.onPendingDatagramCount(count));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
-
- private void setExecutor(Executor executor) {
- mExecutor = executor;
- }
- }
-
+public interface SatelliteStateCallback {
/**
* Called when satellite modem state changes.
* @param state The new satellite modem state.
*/
- public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
- // Base Implementation
- }
-
- /**
- * Called when there are pending datagrams to be received from satellite.
- * @param count Pending datagram count.
- */
- public void onPendingDatagramCount(int count) {
- // Base Implementation
- }
-
- //TODO: Add an API for datagram transfer state update here.
-
- /**@hide*/
- @NonNull
- final ISatelliteStateCallback getBinder() {
- return mBinder;
- }
-
- /**@hide*/
- public void setExecutor(@NonNull Executor executor) {
- mBinder.setExecutor(executor);
- }
+ void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
new file mode 100644
index 0000000..d4fe57a
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+
+/**
+ * A callback class for monitoring satellite position update and datagram transfer state change
+ * events.
+ *
+ * @hide
+ */
+public interface SatelliteTransmissionUpdateCallback {
+ /**
+ * Called when the satellite position changed.
+ *
+ * @param pointingInfo The pointing info containing the satellite location.
+ */
+ void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
+
+ /**
+ * Called when satellite datagram send state changed.
+ *
+ * @param state The new send datagram transfer state.
+ * @param sendPendingCount The number of datagrams that are currently being sent.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ */
+ void onSendDatagramStateChanged(
+ @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+ @SatelliteManager.SatelliteError int errorCode);
+
+ /**
+ * Called when satellite datagram receive state changed.
+ *
+ * @param state The new receive datagram transfer state.
+ * @param receivePendingCount The number of datagrams that are currently pending to be received.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ */
+ void onReceiveDatagramStateChanged(
+ @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
+ @SatelliteManager.SatelliteError int errorCode);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index d93ee21..a780cb9 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -49,10 +49,9 @@
* Listening mode allows the satellite service to listen for incoming pages.
*
* @param enable True to enable satellite listening mode and false to disable.
- * @param isDemoMode Whether demo mode is enabled.
* @param timeout How long the satellite modem should wait for the next incoming page before
* disabling listening mode.
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -64,16 +63,17 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
- in IIntegerConsumer errorCallback);
+ void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
+ in IIntegerConsumer resultCallback);
/**
- * Request to enable or disable the satellite modem. If the satellite modem is enabled,
- * this will also disable the cellular modem, and if the satellite modem is disabled,
- * this will also re-enable the cellular modem.
+ * Request to enable or disable the satellite modem and demo mode. If the satellite modem
+ * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+ * this may also re-enable the cellular modem.
*
- * @param enable True to enable the satellite modem and false to disable.
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param enableSatellite True to enable the satellite modem and false to disable.
+ * @param enableDemoMode True to enable demo mode and false to disable.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -85,13 +85,14 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestSatelliteEnabled(in boolean enabled, in IIntegerConsumer errorCallback);
+ void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
+ in IIntegerConsumer resultCallback);
/**
* Request to get whether the satellite modem is enabled.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* whether the satellite modem is enabled.
*
@@ -105,13 +106,13 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestIsSatelliteEnabled(in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+ void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
/**
* Request to get whether the satellite service is supported on the device.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* whether the satellite service is supported on the device.
*
@@ -125,14 +126,14 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestIsSatelliteSupported(in IIntegerConsumer errorCallback,
+ void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
in IBooleanConsumer callback);
/**
* Request to get the SatelliteCapabilities of the satellite service.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* the SatelliteCapabilities of the satellite service.
*
@@ -146,7 +147,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestSatelliteCapabilities(in IIntegerConsumer errorCallback,
+ void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
in ISatelliteCapabilitiesConsumer callback);
/**
@@ -154,7 +155,7 @@
* The satellite service should report the satellite pointing info via
* ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
*
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -166,13 +167,13 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void startSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
+ void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
/**
* User stopped pointing to the satellite.
* The satellite service should stop reporting satellite pointing info to the framework.
*
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -184,28 +185,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void stopSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
-
- /**
- * Request to get the maximum number of characters per MO text message on satellite.
- *
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
- * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
- * the maximum number of characters per MO text message on satellite.
- *
- * Valid error codes returned:
- * SatelliteError:ERROR_NONE
- * SatelliteError:SERVICE_ERROR
- * SatelliteError:MODEM_ERROR
- * SatelliteError:INVALID_MODEM_STATE
- * SatelliteError:INVALID_ARGUMENTS
- * SatelliteError:RADIO_NOT_AVAILABLE
- * SatelliteError:REQUEST_NOT_SUPPORTED
- * SatelliteError:NO_RESOURCES
- */
- void requestMaxCharactersPerMOTextMessage(in IIntegerConsumer errorCallback,
- in IIntegerConsumer callback);
+ void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
/**
* Provision the device with a satellite provider.
@@ -214,7 +194,8 @@
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param regionId The region ID for the device's current location.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -229,7 +210,8 @@
* SatelliteError:REQUEST_ABORTED
* SatelliteError:NETWORK_TIMEOUT
*/
- void provisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+ void provisionSatelliteService(in String token, in String regionId,
+ in IIntegerConsumer resultCallback);
/**
* Deprovision the device with the satellite provider.
@@ -237,7 +219,7 @@
* Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
*
* @param token The token of the device/subscription to be deprovisioned.
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -252,13 +234,13 @@
* SatelliteError:REQUEST_ABORTED
* SatelliteError:NETWORK_TIMEOUT
*/
- void deprovisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+ void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
/**
* Request to get whether this device is provisioned with a satellite provider.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* whether this device is provisioned with a satellite provider.
*
@@ -272,7 +254,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestIsSatelliteProvisioned(in IIntegerConsumer errorCallback,
+ void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
in IBooleanConsumer callback);
/**
@@ -280,7 +262,7 @@
* The satellite service should check if there are any pending datagrams to be received over
* satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
*
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -297,15 +279,14 @@
* SatelliteError:SATELLITE_NOT_REACHABLE
* SatelliteError:NOT_AUTHORIZED
*/
- void pollPendingSatelliteDatagrams(in IIntegerConsumer errorCallback);
+ void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
/**
* Send datagram over satellite.
*
* @param datagram Datagram to send in byte format.
- * @param isDemoMode Whether demo mode is enabled.
* @param isEmergency Whether this is an emergency datagram.
- * @param errorCallback The callback to receive the error code result of the operation.
+ * @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -323,16 +304,16 @@
* SatelliteError:SATELLITE_NOT_REACHABLE
* SatelliteError:NOT_AUTHORIZED
*/
- void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isDemoMode,
- in boolean isEmergency, in IIntegerConsumer errorCallback);
+ void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
+ in IIntegerConsumer resultCallback);
/**
* Request the current satellite modem state.
* The satellite service should report the current satellite modem state via
* ISatelliteListener#onSatelliteModemStateChanged.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* the current satellite modem state.
*
@@ -346,14 +327,14 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestSatelliteModemState(in IIntegerConsumer errorCallback,
+ void requestSatelliteModemState(in IIntegerConsumer resultCallback,
in IIntegerConsumer callback);
/**
* Request to get whether satellite communication is allowed for the current location.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* whether satellite communication is allowed for the current location.
*
@@ -368,15 +349,15 @@
* SatelliteError:NO_RESOURCES
*/
void requestIsSatelliteCommunicationAllowedForCurrentLocation(
- in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+ in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
/**
* Request to get the time after which the satellite will be visible. This is an int
* representing the duration in seconds after which the satellite will be visible.
* This will return 0 if the satellite is currently visible.
*
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ * This must only be sent when the error is not SatelliteError#ERROR_NONE.
* @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
* the time after which the satellite will be visible.
*
@@ -390,6 +371,6 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestTimeForNextSatelliteVisibility(in IIntegerConsumer errorCallback,
+ void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
in IIntegerConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d966868..5e69215 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -43,10 +43,8 @@
/**
* Indicates that the satellite has pending datagrams for the device to be pulled.
- *
- * @param count Number of pending datagrams.
*/
- void onPendingDatagramCount(in int count);
+ void onPendingDatagrams();
/**
* Indicates that the satellite pointing input has changed.
@@ -61,11 +59,4 @@
* @param state The current satellite modem state.
*/
void onSatelliteModemStateChanged(in SatelliteModemState state);
-
- /**
- * Indicates that the satellite radio technology has changed.
- *
- * @param technology The current satellite radio technology.
- */
- void onSatelliteRadioTechnologyChanged(in NTRadioTechnology technology);
}
diff --git a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
index 83392dd..52a36d8 100644
--- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
+++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
@@ -29,21 +29,4 @@
* Satellite elevation in degrees.
*/
float satelliteElevation;
-
- /**
- * Antenna azimuth in degrees.
- */
- float antennaAzimuth;
-
- /**
- * Angle of rotation about the x axis. This value represents the angle between a plane
- * parallel to the device's screen and a plane parallel to the ground.
- */
- float antennaPitch;
-
- /**
- * Angle of rotation about the y axis. This value represents the angle between a plane
- * perpendicular to the device's screen and a plane parallel to the ground.
- */
- float antennaRoll;
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index 10c2ea3..cd69da1 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -28,18 +28,12 @@
NTRadioTechnology[] supportedRadioTechnologies;
/**
- * Whether satellite modem is always on.
- * This indicates the power impact of keeping it on is very minimal.
- */
- boolean isAlwaysOn;
-
- /**
* Whether UE needs to point to a satellite to send and receive data.
*/
- boolean needsPointingToSatellite;
+ boolean isPointingRequired;
/**
- * Whether UE needs a separate SIM profile to communicate with the satellite network.
+ * The maximum number of bytes per datagram that can be sent over satellite.
*/
- boolean needsSeparateSimProfile;
+ int maxBytesPerOutgoingDatagram;
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 711dcbe..debb394 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -70,20 +70,21 @@
}
@Override
- public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
- int timeout, IIntegerConsumer errorCallback) throws RemoteException {
+ public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+ IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
- .requestSatelliteListeningEnabled(
- enable, isDemoMode, timeout, errorCallback),
+ .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
"requestSatelliteListeningEnabled");
}
@Override
- public void requestSatelliteEnabled(boolean enable, IIntegerConsumer errorCallback)
- throws RemoteException {
+ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+ IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
- () -> SatelliteImplBase.this.requestSatelliteEnabled(enable, errorCallback),
+ () -> SatelliteImplBase.this
+ .requestSatelliteEnabled(
+ enableSatellite, enableDemoMode, errorCallback),
"requestSatelliteEnabled");
}
@@ -131,19 +132,11 @@
}
@Override
- public void requestMaxCharactersPerMOTextMessage(IIntegerConsumer errorCallback,
- IIntegerConsumer callback) throws RemoteException {
+ public void provisionSatelliteService(String token, String regionId,
+ IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
- .requestMaxCharactersPerMOTextMessage(errorCallback, callback),
- "requestMaxCharactersPerMOTextMessage");
- }
-
- @Override
- public void provisionSatelliteService(String token, IIntegerConsumer errorCallback)
- throws RemoteException {
- executeMethodAsync(
- () -> SatelliteImplBase.this.provisionSatelliteService(token, errorCallback),
+ .provisionSatelliteService(token, regionId, errorCallback),
"provisionSatelliteService");
}
@@ -173,12 +166,11 @@
}
@Override
- public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isDemoMode,
- boolean isEmergency, IIntegerConsumer errorCallback) throws RemoteException {
+ public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
+ IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
- .sendSatelliteDatagram(
- datagram, isDemoMode, isEmergency, errorCallback),
+ .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
"sendSatelliteDatagram");
}
@@ -249,7 +241,6 @@
* Listening mode allows the satellite service to listen for incoming pages.
*
* @param enable True to enable satellite listening mode and false to disable.
- * @param isDemoMode Whether demo mode is enabled.
* @param timeout How long the satellite modem should wait for the next incoming page before
* disabling listening mode.
* @param errorCallback The callback to receive the error code result of the operation.
@@ -264,17 +255,18 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
+ public void requestSatelliteListeningEnabled(boolean enable, int timeout,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
/**
- * Request to enable or disable the satellite modem. If the satellite modem is enabled,
- * this will also disable the cellular modem, and if the satellite modem is disabled,
- * this will also re-enable the cellular modem.
+ * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+ * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+ * this may also re-enable the cellular modem.
*
- * @param enable True to enable the satellite modem and false to disable.
+ * @param enableSatellite True to enable the satellite modem and false to disable.
+ * @param enableDemoMode True to enable demo mode and false to disable.
* @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -287,7 +279,8 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- public void requestSatelliteEnabled(boolean enable, @NonNull IIntegerConsumer errorCallback) {
+ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+ @NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
@@ -402,35 +395,13 @@
}
/**
- * Request to get the maximum number of characters per MO text message on satellite.
- *
- * @param errorCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not SatelliteError#ERROR_NONE.
- * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
- * the maximum number of characters per MO text message on satellite.
- *
- * Valid error codes returned:
- * SatelliteError:ERROR_NONE
- * SatelliteError:SERVICE_ERROR
- * SatelliteError:MODEM_ERROR
- * SatelliteError:INVALID_MODEM_STATE
- * SatelliteError:INVALID_ARGUMENTS
- * SatelliteError:RADIO_NOT_AVAILABLE
- * SatelliteError:REQUEST_NOT_SUPPORTED
- * SatelliteError:NO_RESOURCES
- */
- public void requestMaxCharactersPerMOTextMessage(@NonNull IIntegerConsumer errorCallback,
- @NonNull IIntegerConsumer callback) {
- // stub implementation
- }
-
- /**
* Provision the device with a satellite provider.
* This is needed if the provider allows dynamic registration.
* Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
+ * @param regionId The region ID for the device's current location.
* @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -446,7 +417,7 @@
* SatelliteError:REQUEST_ABORTED
* SatelliteError:NETWORK_TIMEOUT
*/
- public void provisionSatelliteService(@NonNull String token,
+ public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
@@ -530,7 +501,6 @@
* Send datagram over satellite.
*
* @param datagram Datagram to send in byte format.
- * @param isDemoMode Whether demo mode is enabled.
* @param isEmergency Whether this is an emergency datagram.
* @param errorCallback The callback to receive the error code result of the operation.
*
@@ -550,8 +520,8 @@
* SatelliteError:SATELLITE_NOT_REACHABLE
* SatelliteError:NOT_AUTHORIZED
*/
- public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isDemoMode,
- boolean isEmergency, @NonNull IIntegerConsumer errorCallback) {
+ public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+ @NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 323fa6f..d0de3ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -68,7 +68,7 @@
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
-import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteStateCallback;
import android.telephony.satellite.SatelliteCapabilities;
@@ -2726,11 +2726,13 @@
*
* @param subId The subId of the subscription to enable or disable the satellite modem for.
* @param enable True to enable the satellite modem and false to disable.
- * @param callback The callback to get the error code of the request.
+ * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+ * @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestSatelliteEnabled(int subId, boolean enable, in IIntegerConsumer callback);
+ void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
+ in IIntegerConsumer callback);
/**
* Request to get whether the satellite modem is enabled.
@@ -2744,17 +2746,6 @@
void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
/**
- * Request to enable or disable the satellite service demo mode.
- *
- * @param subId The subId of the subscription to enable or disable the satellite demo mode for.
- * @param enable True to enable the satellite demo mode and false to disable.
- * @param callback The callback to get the error code of the request.
- */
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
- + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestSatelliteDemoModeEnabled(int subId, boolean enable, in IIntegerConsumer callback);
-
- /**
* Request to get whether the satellite service demo mode is enabled.
*
* @param subId The subId of the subscription to request whether the satellite demo mode is
@@ -2764,7 +2755,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestIsSatelliteDemoModeEnabled(int subId, in ResultReceiver receiver);
+ void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
/**
* Request to get whether the satellite service is supported on the device.
@@ -2787,39 +2778,28 @@
void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
/**
- * Start receiving satellite pointing updates.
+ * Start receiving satellite transmission updates.
*
- * @param subId The subId of the subscription to stop satellite position updates for.
- * @param errorCallback The callback to get the error code of the request.
- * @param callback The callback to handle position updates.
+ * @param subId The subId of the subscription to stop satellite transmission updates for.
+ * @param resultCallback The callback to get the result of the request.
+ * @param callback The callback to handle transmission updates.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void startSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
- in ISatellitePositionUpdateCallback callback);
+ void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+ in ISatelliteTransmissionUpdateCallback callback);
/**
- * Stop receiving satellite pointing updates.
+ * Stop receiving satellite transmission updates.
*
- * @param subId The subId of the subscritpion to stop satellite position updates for.
- * @param errorCallback The callback to get the error code of the request.
- * @param callback The callback that was passed to startSatellitePositionUpdates.
+ * @param subId The subId of the subscritpion to stop satellite transmission updates for.
+ * @param resultCallback The callback to get the result of the request.
+ * @param callback The callback that was passed to startSatelliteTransmissionUpdates.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void stopSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
- in ISatellitePositionUpdateCallback callback);
-
- /**
- * Request to get the maximum number of bytes per datagram that can be sent to satellite.
- *
- * @param subId The subId of the subscription to get the maximum number of characters for.
- * @param receiver Result receiver to get the error code of the request and the requested
- * maximum number of bytes per datagram that can be sent to satellite.
- */
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
- + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestMaxSizePerSendingDatagram(int subId, in ResultReceiver receiver);
+ void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+ in ISatelliteTransmissionUpdateCallback callback);
/**
* Register the subscription with a satellite provider.
@@ -2828,13 +2808,14 @@
* @param subId The subId of the subscription to be provisioned.
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param callback The callback to get the error code of the request.
+ * @param regionId The region ID for the device's current location.
+ * @param callback The callback to get the result of the request.
*
* @return The signal transport used by callers to cancel the provision request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- ICancellationSignal provisionSatelliteService(int subId, in String token,
+ ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
in IIntegerConsumer callback);
/**
@@ -2846,7 +2827,7 @@
*
* @param subId The subId of the subscription to be deprovisioned.
* @param token The token of the device/subscription to be deprovisioned.
- * @param callback The callback to get the error code of the request.
+ * @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2916,15 +2897,13 @@
* Register to receive incoming datagrams over satellite.
*
* @param subId The subId of the subscription to register for incoming satellite datagrams.
- * @param datagramType Type of datagram.
* @param callback The callback to handle the incoming datagrams.
*
* @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForSatelliteDatagram(
- int subId, int datagramType, ISatelliteDatagramCallback callback);
+ int registerForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
/**
* Unregister to stop receiving incoming datagrams over satellite.
@@ -2941,7 +2920,7 @@
* Poll pending satellite datagrams over satellite.
*
* @param subId The subId of the subscription used for receiving datagrams.
- * @param callback The callback to get the error code of the request.
+ * @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2955,7 +2934,7 @@
* @param datagram Datagram to send over satellite.
* @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
* full screen mode.
- * @param callback The callback to get the error code of the request.
+ * @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index d2a6bf2..81efda1 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static org.junit.Assume.assumeFalse;
+
import android.app.AlarmManager;
import android.content.Context;
import android.os.Environment;
@@ -112,6 +114,7 @@
@Before
public void setUp() throws IOException {
+ assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
File dataDir = getContext().getDataDir();
mBigFile = new File(dataDir, BIG_FILE);
}
diff --git a/tests/EnforcePermission/Android.bp b/tests/EnforcePermission/Android.bp
new file mode 100644
index 0000000..719a898
--- /dev/null
+++ b/tests/EnforcePermission/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "frameworks-enforce-permission-test-aidl",
+ srcs: ["aidl/**/*.aidl"],
+}
diff --git a/tests/EnforcePermission/TEST_MAPPING b/tests/EnforcePermission/TEST_MAPPING
new file mode 100644
index 0000000..a1bf42a
--- /dev/null
+++ b/tests/EnforcePermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "EnforcePermissionTests"
+ }
+ ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
copy to tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
index ad783da..1eb773d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,14 +11,15 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
+package android.tests.enforcepermission;
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
- val promptReason: Int = 0,
- val errorMessage: CharSequence? = null,
- val expansionAmount: Float = 0f,
-)
+interface INested {
+ @EnforcePermission("ACCESS_NETWORK_STATE")
+ void ProtectedByAccessNetworkState();
+
+ @EnforcePermission("READ_SYNC_SETTINGS")
+ void ProtectedByReadSyncSettings();
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
new file mode 100644
index 0000000..18e3aec
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission;
+
+interface IProtected {
+ @EnforcePermission("INTERNET")
+ void ProtectedByInternet();
+
+ @EnforcePermission("VIBRATE")
+ void ProtectedByVibrate();
+
+ @EnforcePermission("INTERNET")
+ void ProtectedByInternetAndVibrateImplicitly();
+
+ @EnforcePermission("INTERNET")
+ void ProtectedByInternetAndAccessNetworkStateImplicitly();
+
+ @EnforcePermission("INTERNET")
+ void ProtectedByInternetAndReadSyncSettingsImplicitly();
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
new file mode 100644
index 0000000..a4ac1d7
--- /dev/null
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // 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"],
+}
+
+android_test_helper_app {
+ name: "EnforcePermissionTestHelper",
+ srcs: [
+ "src/**/*.java",
+ ":frameworks-enforce-permission-test-aidl",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml
similarity index 64%
copy from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
copy to tests/EnforcePermission/service-app/AndroidManifest.xml
index 9bf9254..ddafe15 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/tests/EnforcePermission/service-app/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.frameworks.servicestests.install_uses_sdk">
-
- <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
- <!-- This will fail to install, because minExtensionVersion is not met -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
- </uses-sdk>
-
+ package="android.tests.enforcepermission.service">
<application>
+ <service
+ android:name=".TestService"
+ android:exported="true" />
+
+ <service
+ android:name=".NestedTestService"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
new file mode 100644
index 0000000..7879a12
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.tests.enforcepermission.INested;
+import android.util.Log;
+
+public class NestedTestService extends Service {
+ private static final String TAG = "EnforcePermission.NestedTestService";
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind");
+ return mBinder;
+ }
+
+ private final INested.Stub mBinder = new INested.Stub() {
+ @Override
+ @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void ProtectedByAccessNetworkState() {
+ ProtectedByAccessNetworkState_enforcePermission();
+ }
+
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_SYNC_SETTINGS)
+ public void ProtectedByReadSyncSettings() {
+ ProtectedByReadSyncSettings_enforcePermission();
+ }
+ };
+}
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
new file mode 100644
index 0000000..e9b897d
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.INested;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class TestService extends Service {
+
+ private static final String TAG = "EnforcePermission.TestService";
+ private volatile ServiceConnection mNestedServiceConnection;
+
+ @Override
+ public void onCreate() {
+ mNestedServiceConnection = new ServiceConnection();
+ Intent intent = new Intent(this, NestedTestService.class);
+ boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE);
+ if (!bound) {
+ Log.wtf(TAG, "bindService() on NestedTestService failed");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ unbindService(mNestedServiceConnection);
+ }
+
+ private static final class ServiceConnection implements android.content.ServiceConnection {
+ private volatile CompletableFuture<INested> mFuture = new CompletableFuture<>();
+
+ public INested get() {
+ try {
+ return mFuture.get(1, TimeUnit.SECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ throw new RuntimeException("Unable to reach NestedTestService: " + e.getMessage());
+ }
+ }
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mFuture.complete(INested.Stub.asInterface(service));
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mFuture = new CompletableFuture<>();
+ }
+ };
+
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private final IProtected.Stub mBinder = new IProtected.Stub() {
+ @Override
+ @EnforcePermission(android.Manifest.permission.INTERNET)
+ public void ProtectedByInternet() {
+ ProtectedByInternet_enforcePermission();
+ }
+
+ @Override
+ @EnforcePermission(android.Manifest.permission.VIBRATE)
+ public void ProtectedByVibrate() {
+ ProtectedByVibrate_enforcePermission();
+ }
+
+ @Override
+ @EnforcePermission(android.Manifest.permission.INTERNET)
+ public void ProtectedByInternetAndVibrateImplicitly() {
+ ProtectedByInternetAndVibrateImplicitly_enforcePermission();
+
+ ProtectedByVibrate();
+ }
+
+ @Override
+ @EnforcePermission(android.Manifest.permission.INTERNET)
+ public void ProtectedByInternetAndAccessNetworkStateImplicitly() throws RemoteException {
+ ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission();
+
+ mNestedServiceConnection.get().ProtectedByAccessNetworkState();
+
+ }
+
+ @Override
+ @EnforcePermission(android.Manifest.permission.INTERNET)
+ public void ProtectedByInternetAndReadSyncSettingsImplicitly() throws RemoteException {
+ ProtectedByInternetAndReadSyncSettingsImplicitly_enforcePermission();
+
+ mNestedServiceConnection.get().ProtectedByReadSyncSettings();
+ }
+ };
+}
diff --git a/tests/EnforcePermission/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp
new file mode 100644
index 0000000..cd53854
--- /dev/null
+++ b/tests/EnforcePermission/test-app/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "EnforcePermissionTests",
+ srcs: [
+ "src/**/*.java",
+ ":frameworks-enforce-permission-test-aidl",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ data: [
+ ":EnforcePermissionTestHelper",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+}
diff --git a/tests/EnforcePermission/test-app/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..4a0c6a8
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.tests.enforcepermission.tests">
+
+ <!-- Expected for the tests (not actually used) -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+
+ <queries>
+ <package android:name="android.tests.enforcepermission.service" />
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/test-app/AndroidTest.xml b/tests/EnforcePermission/test-app/AndroidTest.xml
new file mode 100644
index 0000000..120381a
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs EnforcePermission End-to-End Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+ <option name="test-file-name" value="EnforcePermissionTests.apk"/>
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <option name="test-tag" value="EnforcePermissionTests"/>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.tests.enforcepermission.tests"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
new file mode 100644
index 0000000..d2a4a03
--- /dev/null
+++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission.tests;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class ServiceTest {
+
+ private static final String TAG = "EnforcePermission.Tests";
+ private static final String SERVICE_NAME = "android.tests.enforcepermission.service";
+ private static final int SERVICE_TIMEOUT_SEC = 5;
+
+ private Context mContext;
+ private volatile ServiceConnection mServiceConnection;
+
+ @Before
+ public void bindTestService() throws Exception {
+ Log.d(TAG, "bindTestService");
+ mContext = InstrumentationRegistry.getTargetContext();
+ mServiceConnection = new ServiceConnection();
+ Intent intent = new Intent();
+ intent.setClassName(SERVICE_NAME, SERVICE_NAME + ".TestService");
+ assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+ }
+
+ @After
+ public void unbindTestService() throws Exception {
+ mContext.unbindService(mServiceConnection);
+ }
+
+ private static final class ServiceConnection implements android.content.ServiceConnection {
+ private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mFuture.complete(IProtected.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mFuture = new CompletableFuture<>();
+ }
+
+ public IProtected get() {
+ try {
+ return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ throw new RuntimeException("Unable to reach TestService: " + e.toString());
+ }
+ }
+ }
+
+ @Test
+ public void testImmediatePermissionGranted_succeeds()
+ throws RemoteException {
+ mServiceConnection.get().ProtectedByInternet();
+ }
+
+ @Test
+ public void testImmediatePermissionNotGranted_fails()
+ throws RemoteException {
+ final Exception ex = assertThrows(SecurityException.class,
+ () -> mServiceConnection.get().ProtectedByVibrate());
+ assertThat(ex.getMessage(), containsString("VIBRATE"));
+ }
+
+ @Test
+ public void testImmediatePermissionGrantedButImplicitLocalNotGranted_fails()
+ throws RemoteException {
+ final Exception ex = assertThrows(SecurityException.class,
+ () -> mServiceConnection.get().ProtectedByInternetAndVibrateImplicitly());
+ assertThat(ex.getMessage(), containsString("VIBRATE"));
+ }
+
+ @Test
+ public void testImmediatePermissionGrantedButImplicitNestedNotGranted_fails()
+ throws RemoteException {
+ final Exception ex = assertThrows(SecurityException.class,
+ () -> mServiceConnection.get()
+ .ProtectedByInternetAndAccessNetworkStateImplicitly());
+ assertThat(ex.getMessage(), containsString("ACCESS_NETWORK_STATE"));
+ }
+
+ @Test
+ public void testImmediatePermissionGrantedAndImplicitNestedGranted_succeeds()
+ throws RemoteException {
+ mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly();
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
new file mode 100644
index 0000000..3289bc6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 8fdbb64..d0dc42f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -72,6 +73,10 @@
/** {@inheritDoc} */
@Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
new file mode 100644
index 0000000..f75d9ee
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) {
+ @FlakyTest(bugId = 273696733)
+ @Test
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
new file mode 100644
index 0000000..4aa78d4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) :
+ OpenAppFromNotificationCold(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
new file mode 100644
index 0000000..9679059
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index e704cc2..46ebfed 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
@@ -141,14 +142,118 @@
HashMap<String, LocaleInfo> result =
LocaleStore.convertExplicitLocales(locales, supportedLocale);
-
assertEquals("en", result.get("en").getId());
assertEquals("en-US", result.get("en-US").getId());
assertNull(result.get("en-Latn-US"));
}
+ @Test
+ public void getLevelLocales_languageTier_returnAllSupportLanguages() {
+ LocaleList testSupportedLocales =
+ LocaleList.forLanguageTags(
+ "en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN");
+
+ Set<String> ignorableLocales = new HashSet<>();
+ ignorableLocales.add("zh-Hant-HK");
+ LocaleInfo parent = null;
+
+ Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+ null, ignorableLocales, parent, false, testSupportedLocales);
+
+ assertEquals(5, localeInfos.size());
+ localeInfos.forEach(localeInfo -> {
+ assertTrue(localeInfo.getLocale().getCountry().isEmpty());
+ });
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("en")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("zh-Hant")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("ja")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("bn")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("ks-Arab")));
+ }
+
+ @Test
+ public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() {
+ LocaleList testSupportedLocales =
+ LocaleList.forLanguageTags(
+ "en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN");
+ Set<String> ignorableLocales = new HashSet<>();
+ ignorableLocales.add("zh-Hant-HK");
+ LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en"));
+
+ Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+ null, ignorableLocales, parent, false, testSupportedLocales);
+
+ assertEquals(3, localeInfos.size());
+ localeInfos.forEach(localeInfo -> {
+ assertEquals("en", localeInfo.getLocale().getLanguage());
+ });
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("en-US")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("en-GB")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("en-ZA")));
+ }
+
+ @Test
+ public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() {
+ LocaleList testSupportedLocales =
+ LocaleList.forLanguageTags(
+ "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+ Set<String> ignorableLocales = new HashSet<>();
+ ignorableLocales.add("zh-Hant-HK");
+ LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn"));
+
+ Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+ null, ignorableLocales, parent, false, testSupportedLocales);
+
+ assertEquals(1, localeInfos.size());
+ assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag());
+ }
+
+ @Test
+ public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() {
+ LocaleList testSupportedLocales =
+ LocaleList.forLanguageTags(
+ "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+ Set<String> ignorableLocales = new HashSet<>();
+ ignorableLocales.add("bn-IN");
+ LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+ Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+ null, ignorableLocales, parent, false, testSupportedLocales);
+
+ assertEquals(0, localeInfos.size());
+ }
+
+ @Test
+ public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() {
+ LocaleList testSupportedLocales =
+ LocaleList.forLanguageTags(
+ "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+ Set<String> ignorableLocales = new HashSet<>();
+ ignorableLocales.add("en-US");
+ LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+ Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+ null, ignorableLocales, parent, false, testSupportedLocales);
+
+ assertEquals(3, localeInfos.size());
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab")));
+ assertTrue(localeInfos.stream().anyMatch(
+ info -> info.getLocale().toLanguageTag().equals("bn-IN")));
+ }
+
private ArrayList<LocaleInfo> getFakeSupportedLocales() {
- String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+ String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"};
ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
for (String localeTag : locales) {
supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index f183a9b..b7f5b15 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -20,7 +20,6 @@
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.AlwaysOnHotwordDetector.Callback;
import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
-import android.service.voice.HotwordDetector;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
@@ -84,24 +83,16 @@
break;
case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:
Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED");
- try {
- Intent enroll = mHotwordDetector.createEnrollIntent();
- Log.i(TAG, "Need to enroll with " + enroll);
- } catch (HotwordDetector.IllegalDetectorStateException e) {
- Log.e(TAG, "createEnrollIntent failed", e);
- }
+ Intent enroll = mHotwordDetector.createEnrollIntent();
+ Log.i(TAG, "Need to enroll with " + enroll);
break;
case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
- try {
- if (mHotwordDetector.startRecognition(
- AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
- Log.i(TAG, "startRecognition succeeded");
- } else {
- Log.i(TAG, "startRecognition failed");
- }
- } catch (HotwordDetector.IllegalDetectorStateException e) {
- Log.e(TAG, "startRecognition failed", e);
+ if (mHotwordDetector.startRecognition(
+ AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
+ Log.i(TAG, "startRecognition succeeded");
+ } else {
+ Log.i(TAG, "startRecognition failed");
}
break;
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 5fdfb66..2ce2167 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -209,6 +209,8 @@
AddOptionalFlag("--compile-sdk-version-name",
"Version name to inject into the AndroidManifest.xml if none is present.",
&options_.manifest_fixer_options.compile_sdk_version_codename);
+ AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.",
+ &options_.manifest_fixer_options.fingerprint_prefixes);
AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
&shared_lib_);
AddOptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib_);
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c66f4e5..a43bf1b 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -2120,6 +2120,33 @@
}
};
+/** Represents <install-constraints> elements. **/
+class InstallConstraints : public ManifestExtractor::Element {
+ public:
+ InstallConstraints() = default;
+ std::vector<std::string> fingerprint_prefixes;
+
+ void Extract(xml::Element* element) override {
+ for (xml::Element* child : element->GetChildElements()) {
+ if (child->name == "fingerprint-prefix") {
+ xml::Attribute* attr = child->FindAttribute(kAndroidNamespace, "value");
+ if (attr) {
+ fingerprint_prefixes.push_back(attr->value);
+ }
+ }
+ }
+ }
+
+ void Print(text::Printer* printer) override {
+ if (!fingerprint_prefixes.empty()) {
+ printer->Print(StringPrintf("install-constraints:\n"));
+ for (const auto& prefix : fingerprint_prefixes) {
+ printer->Print(StringPrintf(" fingerprint-prefix='%s'\n", prefix.c_str()));
+ }
+ }
+ }
+};
+
/** Represents <original-package> elements. **/
class OriginalPackage : public ManifestExtractor::Element {
public:
@@ -2869,7 +2896,7 @@
constexpr const char* GetExpectedTagForType() {
// This array does not appear at runtime, as GetExpectedTagForType function is used by compiler
// to inject proper 'expected_tag' into ElementCast.
- std::array<std::pair<const char*, bool>, 37> tags = {
+ std::array<std::pair<const char*, bool>, 38> tags = {
std::make_pair("action", std::is_same<Action, T>::value),
std::make_pair("activity", std::is_same<Activity, T>::value),
std::make_pair("additional-certificate", std::is_same<AdditionalCertificate, T>::value),
@@ -2878,6 +2905,7 @@
std::make_pair("compatible-screens", std::is_same<CompatibleScreens, T>::value),
std::make_pair("feature-group", std::is_same<FeatureGroup, T>::value),
std::make_pair("input-type", std::is_same<InputType, T>::value),
+ std::make_pair("install-constraints", std::is_same<InstallConstraints, T>::value),
std::make_pair("intent-filter", std::is_same<IntentFilter, T>::value),
std::make_pair("meta-data", std::is_same<MetaData, T>::value),
std::make_pair("manifest", std::is_same<Manifest, T>::value),
@@ -2948,6 +2976,7 @@
{"compatible-screens", &CreateType<CompatibleScreens>},
{"feature-group", &CreateType<FeatureGroup>},
{"input-type", &CreateType<InputType>},
+ {"install-constraints", &CreateType<InstallConstraints>},
{"intent-filter", &CreateType<IntentFilter>},
{"manifest", &CreateType<Manifest>},
{"meta-data", &CreateType<MetaData>},
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 56d9075..53f0abe 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -749,6 +749,23 @@
attr->value = options_.compile_sdk_version_codename.value();
}
+ if (!options_.fingerprint_prefixes.empty()) {
+ xml::Element* install_constraints_el = root->FindChild({}, "install-constraints");
+ if (install_constraints_el == nullptr) {
+ std::unique_ptr<xml::Element> install_constraints = std::make_unique<xml::Element>();
+ install_constraints->name = "install-constraints";
+ install_constraints_el = install_constraints.get();
+ root->AppendChild(std::move(install_constraints));
+ }
+ for (const std::string& prefix : options_.fingerprint_prefixes) {
+ std::unique_ptr<xml::Element> prefix_el = std::make_unique<xml::Element>();
+ prefix_el->name = "fingerprint-prefix";
+ xml::Attribute* attr = prefix_el->FindOrCreateAttribute(xml::kSchemaAndroid, "value");
+ attr->value = prefix;
+ install_constraints_el->AppendChild(std::move(prefix_el));
+ }
+ }
+
xml::XmlActionExecutor executor;
if (!BuildRules(&executor, context->GetDiagnostics())) {
return false;
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 90f5177..175ab6f 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,11 +18,10 @@
#define AAPT_LINK_MANIFESTFIXER_H
#include <string>
+#include <vector>
#include "android-base/macros.h"
-
#include "process/IResourceTableConsumer.h"
-
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
@@ -75,6 +74,9 @@
// 'android:compileSdkVersionCodename' in the <manifest> tag.
std::optional<std::string> compile_sdk_version_codename;
+ // The fingerprint prefixes to be added to the <install-constraints> tag.
+ std::vector<std::string> fingerprint_prefixes;
+
// Whether validation errors should be treated only as warnings. If this is 'true', then an
// incorrect node will not result in an error, but only as a warning, and the parsing will
// continue.
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 7180ae6..1b8f05b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -965,6 +965,63 @@
ASSERT_THAT(manifest, IsNull());
}
+TEST_F(ManifestFixerTest, InsertFingerprintPrefixIfNotExist) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ </manifest>)";
+ ManifestFixerOptions options;
+ options.fingerprint_prefixes = {"foo", "bar"};
+
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+ xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+ ASSERT_THAT(install_constraints, NotNull());
+ std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+ EXPECT_EQ(fingerprint_prefixes.size(), 2);
+ xml::Attribute* attr;
+ EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+ attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("foo"));
+ EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+ attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("bar"));
+}
+
+TEST_F(ManifestFixerTest, AppendFingerprintPrefixIfExists) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <install-constraints>
+ <fingerprint-prefix android:value="foo" />
+ </install-constraints>
+ </manifest>)";
+ ManifestFixerOptions options;
+ options.fingerprint_prefixes = {"bar", "baz"};
+
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+ xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+ ASSERT_THAT(install_constraints, NotNull());
+ std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+ EXPECT_EQ(fingerprint_prefixes.size(), 3);
+ xml::Attribute* attr;
+ EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+ attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("foo"));
+ EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+ attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("bar"));
+ EXPECT_THAT(fingerprint_prefixes[2]->name, StrEq("fingerprint-prefix"));
+ attr = fingerprint_prefixes[2]->FindAttribute(xml::kSchemaAndroid, "value");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("baz"));
+}
+
TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
std::string input = R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 4809bef..63e471b 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -17,7 +17,11 @@
package android.net.wifi.sharedconnectivity.app;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,6 +41,7 @@
public final class SharedConnectivitySettingsState implements Parcelable {
private final boolean mInstantTetherEnabled;
+ private final PendingIntent mInstantTetherSettingsPendingIntent;
private final Bundle mExtras;
/**
@@ -44,9 +49,13 @@
*/
public static final class Builder {
private boolean mInstantTetherEnabled;
+ private Intent mInstantTetherSettingsIntent;
+ private final Context mContext;
private Bundle mExtras;
- public Builder() {}
+ public Builder(@NonNull Context context) {
+ mContext = context;
+ }
/**
* Sets the state of Instant Tether in settings
@@ -60,6 +69,20 @@
}
/**
+ * Sets the intent that will open the Instant Tether settings page.
+ * The intent will be stored as a {@link PendingIntent} in the settings object. The pending
+ * intent will be set as {@link PendingIntent#FLAG_IMMUTABLE} and
+ * {@link PendingIntent#FLAG_ONE_SHOT}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setInstantTetherSettingsPendingIntent(@NonNull Intent intent) {
+ mInstantTetherSettingsIntent = intent;
+ return this;
+ }
+
+ /**
* Sets the extras bundle
*
* @return Returns the Builder object.
@@ -77,12 +100,22 @@
*/
@NonNull
public SharedConnectivitySettingsState build() {
- return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras);
+ if (mInstantTetherSettingsIntent != null) {
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+ mInstantTetherSettingsIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+ return new SharedConnectivitySettingsState(mInstantTetherEnabled,
+ pendingIntent, mExtras);
+ }
+ return new SharedConnectivitySettingsState(mInstantTetherEnabled, null, mExtras);
}
}
- private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) {
+ private SharedConnectivitySettingsState(boolean instantTetherEnabled,
+ PendingIntent pendingIntent, Bundle extras) {
+
mInstantTetherEnabled = instantTetherEnabled;
+ mInstantTetherSettingsPendingIntent = pendingIntent;
mExtras = extras;
}
@@ -96,6 +129,16 @@
}
/**
+ * Gets the pending intent to open Instant Tether settings page.
+ *
+ * @return Returns the pending intent that opens the settings page, null if none.
+ */
+ @Nullable
+ public PendingIntent getInstantTetherSettingsPendingIntent() {
+ return mInstantTetherSettingsPendingIntent;
+ }
+
+ /**
* Gets the extras Bundle.
*
* @return Returns a Bundle object.
@@ -109,12 +152,14 @@
public boolean equals(Object obj) {
if (!(obj instanceof SharedConnectivitySettingsState)) return false;
SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj;
- return mInstantTetherEnabled == other.isInstantTetherEnabled();
+ return mInstantTetherEnabled == other.isInstantTetherEnabled()
+ && Objects.equals(mInstantTetherSettingsPendingIntent,
+ other.getInstantTetherSettingsPendingIntent());
}
@Override
public int hashCode() {
- return Objects.hash(mInstantTetherEnabled);
+ return Objects.hash(mInstantTetherEnabled, mInstantTetherSettingsPendingIntent);
}
@Override
@@ -124,6 +169,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0);
dest.writeBoolean(mInstantTetherEnabled);
dest.writeBundle(mExtras);
}
@@ -135,8 +181,10 @@
*/
@NonNull
public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
- return new SharedConnectivitySettingsState(in.readBoolean(),
- in.readBundle());
+ PendingIntent pendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+ boolean instantTetherEnabled = in.readBoolean();
+ Bundle extras = in.readBundle();
+ return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras);
}
@NonNull
@@ -156,6 +204,7 @@
public String toString() {
return new StringBuilder("SharedConnectivitySettingsState[")
.append("instantTetherEnabled=").append(mInstantTetherEnabled)
+ .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
.append("extras=").append(mExtras.toString())
.append("]").toString();
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 57108e4..87ca99f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -68,9 +68,7 @@
new RemoteCallbackList<>();
private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
- private SharedConnectivitySettingsState mSettingsState =
- new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false)
- .setExtras(Bundle.EMPTY).build();
+ private SharedConnectivitySettingsState mSettingsState = null;
private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
new HotspotNetworkConnectionStatus.Builder()
.setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
@@ -202,6 +200,13 @@
@Override
public SharedConnectivitySettingsState getSettingsState() {
checkPermissions();
+ // Done lazily since creating it needs a context.
+ if (mSettingsState == null) {
+ mSettingsState = new SharedConnectivitySettingsState
+ .Builder(getApplicationContext())
+ .setInstantTetherEnabled(false)
+ .setExtras(Bundle.EMPTY).build();
+ }
return mSettingsState;
}
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index c9105f7..7a29969 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -36,6 +36,7 @@
static_libs: [
"androidx.test.rules",
+ "androidx.test.core",
"frameworks-base-testutils",
"guava",
"mockito-target-minus-junit4",
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 7578dfd..71239087 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -497,8 +497,9 @@
@Test
public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
- SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
- .setInstantTetherEnabled(true).setExtras(new Bundle()).build();
+ SharedConnectivitySettingsState state =
+ new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+ .setExtras(new Bundle()).build();
manager.setService(mService);
when(mService.getSettingsState()).thenReturn(state);
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
index 752b749..5e17dfb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -18,8 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
+import android.content.Intent;
import android.os.Parcel;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -30,8 +32,12 @@
@SmallTest
public class SharedConnectivitySettingsStateTest {
private static final boolean INSTANT_TETHER_STATE = true;
+ private static final String INTENT_ACTION = "instant.tether.settings";
private static final boolean INSTANT_TETHER_STATE_1 = false;
+ private static final String INTENT_ACTION_1 = "instant.tether.settings1";
+
+
/**
* Verifies parcel serialization/deserialization.
*/
@@ -39,16 +45,11 @@
public void testParcelOperation() {
SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
- Parcel parcelW = Parcel.obtain();
- state.writeToParcel(parcelW, 0);
- byte[] bytes = parcelW.marshall();
- parcelW.recycle();
-
- Parcel parcelR = Parcel.obtain();
- parcelR.unmarshall(bytes, 0, bytes.length);
- parcelR.setDataPosition(0);
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
SharedConnectivitySettingsState fromParcel =
- SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
+ SharedConnectivitySettingsState.CREATOR.createFromParcel(parcel);
assertThat(fromParcel).isEqualTo(state);
assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode());
@@ -66,6 +67,10 @@
SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
.setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
assertThat(builder.build()).isNotEqualTo(state1);
+
+ builder = buildSettingsStateBuilder()
+ .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION_1));
+ assertThat(builder.build()).isNotEqualTo(state1);
}
/**
@@ -86,7 +91,9 @@
}
private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
- return new SharedConnectivitySettingsState.Builder()
- .setInstantTetherEnabled(INSTANT_TETHER_STATE);
+ return new SharedConnectivitySettingsState.Builder(
+ ApplicationProvider.getApplicationContext())
+ .setInstantTetherEnabled(INSTANT_TETHER_STATE)
+ .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION));
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index b8b6b767..514ba3c 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -75,9 +75,6 @@
.addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
NETWORK_PROVIDER_INFO).build();
private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
- private static final SharedConnectivitySettingsState SETTINGS_STATE =
- new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
- .setExtras(Bundle.EMPTY).build();
private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
.setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
@@ -155,10 +152,11 @@
SharedConnectivityService service = createService();
ISharedConnectivityService.Stub binder =
(ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
- service.setSettingsState(SETTINGS_STATE);
+ service.setSettingsState(buildSettingsState());
- assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE);
+ assertThat(binder.getSettingsState()).isEqualTo(buildSettingsState());
}
@Test
@@ -232,4 +230,10 @@
service.attachBaseContext(mContext);
return service;
}
+
+ private SharedConnectivitySettingsState buildSettingsState() {
+ return new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+ .setInstantTetherSettingsPendingIntent(new Intent())
+ .setExtras(Bundle.EMPTY).build();
+ }
}