Merge "Fix the translation doesn't work" 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/core/api/current.txt b/core/api/current.txt
index c1c44f0..02de1cd 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";
@@ -11403,7 +11404,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();
@@ -27345,7 +27346,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
@@ -33864,7 +33867,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);
@@ -40591,7 +40594,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 {
@@ -40640,7 +40643,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 {
@@ -41558,7 +41561,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);
@@ -41568,7 +41570,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";
}
@@ -41693,18 +41695,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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d573e33..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";
@@ -10135,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);
}
}
@@ -12994,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
@@ -13186,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 {
@@ -13203,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();
@@ -13273,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 0123cc9..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;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 87643b2..2751b54 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);
}
@@ -6653,12 +6657,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/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/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..e78fb17 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -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/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/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 328b0ae..b9c671a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8666,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/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 722dd08..e6b3069 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -125,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) {
@@ -1194,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
@@ -1270,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>
*
@@ -1284,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.
@@ -1306,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>
*
@@ -1320,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.
@@ -2283,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,
@@ -2416,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/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/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/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/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 2830fb7..ffa15f0 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -827,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");
}
@@ -869,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();
}
}
@@ -935,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();
@@ -959,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");
}
@@ -1029,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;
@@ -1069,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;
@@ -1106,8 +1049,7 @@
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Override
- public boolean startRecognition()
- throws IllegalDetectorStateException {
+ public boolean startRecognition() {
return startRecognition(0);
}
@@ -1117,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");
}
@@ -1179,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");
}
@@ -1221,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");
}
@@ -1259,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");
}
@@ -1298,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);
@@ -1330,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);
@@ -1362,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);
@@ -1388,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");
}
@@ -1489,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/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/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/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/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 1e6c1ff..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;
@@ -415,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/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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 611035e..70a1354 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -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/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 c10612e..1cb56e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4931,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/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 5d303cf..0faf62e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -321,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/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
deleted file mode 120000
index 707dfcf..0000000
--- a/data/keyboards/Vendor_004c_Product_0265.idc
+++ /dev/null
@@ -1 +0,0 @@
-Vendor_05ac_Product_0265.idc
\ No newline at end of file
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_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
index d72de64..520d188 100644
--- a/data/keyboards/Vendor_05ac_Product_0265.idc
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -13,9 +13,9 @@
# limitations under the License.
#
-# Apple Magic Trackpad 2 configuration file
-# Bluetooth vendor ID 004c
-# USB vendor ID 05ac
+# Apple Magic Trackpad 2 (USB) configuration file
+#
+# WHEN MODIFYING, also change the Bluetooth file (Vendor_004c_Product_0265.idc)
#
gestureProp.Pressure_Calibration_Offset = 30
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/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/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/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/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/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 2e86448..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;
}
@@ -216,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? "
@@ -441,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.
@@ -474,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);
@@ -493,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/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/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/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/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/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..4e380c4 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,7 +1505,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(@NonNull @TvInputManager.TvMessageType String type,
@NonNull Bundle 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/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/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 440c6e5..ca84265 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -58,8 +58,5 @@
<intent>
<action android:name="android.intent.action.VOICE_COMMAND" />
</intent>
- <!--intent>
- <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
- </intent-->
</queries>
</manifest>
\ No newline at end of file
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/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/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/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/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/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/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/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/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..661b2b6 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
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/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index d716784..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
@@ -34,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
@@ -112,16 +111,18 @@
launch {
viewModel.show.collect {
// Reset Security Container entirely.
- view.visibility = View.VISIBLE
- securityContainerController.onBouncerVisibilityChanged(
- /* isVisible= */ true
- )
- securityContainerController.reinflateViewFlipper()
- securityContainerController.showPrimarySecurityScreen(
- /* turningOff= */ false
- )
- securityContainerController.appear()
- securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+ 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)
+ }
}
}
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 68910c6..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
@@ -70,7 +70,7 @@
/** Observe whether we should update fps is showing. */
val shouldUpdateSideFps: Flow<Unit> =
merge(
- interactor.startingToHide,
+ 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/log/dagger/NotificationRemoteInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
new file mode 100644
index 0000000..3a639a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+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/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/multishade/shared/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
new file mode 100644
index 0000000..49ac64c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 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/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/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 274377f..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,9 @@
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;
@@ -70,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;
@@ -112,7 +108,6 @@
public NotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
PowerManager powerManager,
- IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
@@ -126,7 +121,6 @@
UserTracker userTracker) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
- mDreamManager = dreamManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mStatusBarStateController = statusBarStateController;
@@ -287,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);
}
@@ -365,16 +361,6 @@
}
}
}
-
- 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();
@@ -424,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/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/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/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/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index e66be08..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,7 +93,7 @@
}
@Test
- fun shouldUpdateSideFps() = runTest {
+ fun shouldUpdateSideFps_show() = runTest {
var count = 0
val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
repository.setPrimaryShow(true)
@@ -104,6 +104,18 @@
}
@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)
+ job.cancel()
+ }
+
+ @Test
fun sideFpsShowing() = runTest {
var sideFpsIsShowing = false
val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
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/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/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/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 8acf507..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
@@ -54,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;
@@ -94,8 +93,6 @@
@Mock
PowerManager mPowerManager;
@Mock
- IDreamManager mDreamManager;
- @Mock
AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock
StatusBarStateController mStatusBarStateController;
@@ -133,7 +130,6 @@
new NotificationInterruptStateProviderImpl(
mContext.getContentResolver(),
mPowerManager,
- mDreamManager,
mAmbientDisplayConfiguration,
mBatteryController,
mStatusBarStateController,
@@ -157,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);
}
@@ -359,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();
}
@@ -539,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))
@@ -558,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))
@@ -577,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))
@@ -599,7 +595,7 @@
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))
@@ -621,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))
@@ -651,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))
@@ -673,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))
@@ -695,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))
@@ -718,7 +714,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);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -735,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);
@@ -754,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);
@@ -775,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);
@@ -800,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);
@@ -821,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);
@@ -846,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);
@@ -892,7 +888,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);
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/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index e185922..8e3988b 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),
@@ -1650,7 +1653,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 +1663,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 +1678,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 +1852,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/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 2a1e860..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;
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 3dac04c..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);
@@ -589,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;
@@ -614,7 +611,7 @@
}
/*package*/ boolean safeDevicesContains(int device) {
- return mSafeMediaVolumeDevices.containsKey(device);
+ return mSafeMediaVolumeDevices.indexOfKey(device) >= 0;
}
/*package*/ void invalidatPendingVolumeCommand() {
@@ -665,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);
@@ -721,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/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/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/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5e0a180..8417049 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -62,7 +62,7 @@
public class ValidateNotificationPeople implements NotificationSignalExtractor {
// Using a shorter log tag since setprop has a limit of 32chars on variable name.
private static final String TAG = "ValidateNoPeople";
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
@@ -105,12 +105,13 @@
private int mEvictionCount;
private NotificationUsageStats mUsageStats;
+ @Override
public void initialize(Context context, NotificationUsageStats usageStats) {
if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
mUserToContextMap = new ArrayMap<>();
mBaseContext = context;
mUsageStats = usageStats;
- mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+ mPeopleCache = new LruCache<>(PEOPLE_CACHE_SIZE);
mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
if (mEnabled) {
@@ -134,7 +135,7 @@
// For tests: just do the setting of various local variables without actually doing work
@VisibleForTesting
protected void initForTests(Context context, NotificationUsageStats usageStats,
- LruCache peopleCache) {
+ LruCache<String, LookupResult> peopleCache) {
mUserToContextMap = new ArrayMap<>();
mBaseContext = context;
mUsageStats = usageStats;
@@ -142,6 +143,7 @@
mEnabled = true;
}
+ @Override
public RankingReconsideration process(NotificationRecord record) {
if (!mEnabled) {
if (VERBOSE) Slog.i(TAG, "disabled");
@@ -272,7 +274,7 @@
}
if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
- final LinkedList<String> pendingLookups = new LinkedList<String>();
+ final LinkedList<String> pendingLookups = new LinkedList<>();
int personIdx = 0;
for (String handle : people) {
if (TextUtils.isEmpty(handle)) continue;
@@ -320,7 +322,6 @@
return Integer.toString(userId) + ":" + handle;
}
- // VisibleForTesting
public static String[] getExtraPeople(Bundle extras) {
String[] peopleList = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE_LIST);
String[] legacyPeople = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE);
@@ -417,101 +418,6 @@
return null;
}
- private LookupResult resolvePhoneContact(Context context, final String number) {
- Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(number));
- return searchContacts(context, phoneUri);
- }
-
- private LookupResult resolveEmailContact(Context context, final String email) {
- Uri numberUri = Uri.withAppendedPath(
- ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
- Uri.encode(email));
- return searchContacts(context, numberUri);
- }
-
- @VisibleForTesting
- LookupResult searchContacts(Context context, Uri lookupUri) {
- LookupResult lookupResult = new LookupResult();
- final Uri corpLookupUri =
- ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
- if (corpLookupUri == null) {
- addContacts(lookupResult, context, lookupUri);
- } else {
- addWorkContacts(lookupResult, context, corpLookupUri);
- }
- return lookupResult;
- }
-
- @VisibleForTesting
- // Performs a contacts search using searchContacts, and then follows up by looking up
- // any phone numbers associated with the resulting contact information and merge those
- // into the lookup result as well. Will have no additional effect if the contact does
- // not have any phone numbers.
- LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
- LookupResult lookupResult = searchContacts(context, lookupUri);
- String phoneLookupKey = lookupResult.getPhoneLookupKey();
- if (phoneLookupKey != null) {
- String selection = Contacts.LOOKUP_KEY + " = ?";
- String[] selectionArgs = new String[] { phoneLookupKey };
- try (Cursor cursor = context.getContentResolver().query(
- ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
- selection, selectionArgs, /* sortOrder= */ null)) {
- if (cursor == null) {
- Slog.w(TAG, "Cursor is null when querying contact phone number.");
- return lookupResult;
- }
-
- while (cursor.moveToNext()) {
- lookupResult.mergePhoneNumber(cursor);
- }
- } catch (Throwable t) {
- Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
- }
- }
- return lookupResult;
- }
-
- private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
- final int workUserId = findWorkUserId(context);
- if (workUserId == -1) {
- Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
- return;
- }
- final Uri corpLookupUriWithUserId =
- ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
- addContacts(lookupResult, context, corpLookupUriWithUserId);
- }
-
- /** Returns the user ID of the managed profile or -1 if none is found. */
- private int findWorkUserId(Context context) {
- final UserManager userManager = context.getSystemService(UserManager.class);
- final int[] profileIds =
- userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
- for (int profileId : profileIds) {
- if (userManager.isManagedProfile(profileId)) {
- return profileId;
- }
- }
- return -1;
- }
-
- /** Modifies the given lookup result to add contacts found at the given URI. */
- private void addContacts(LookupResult lookupResult, Context context, Uri uri) {
- try (Cursor c = context.getContentResolver().query(
- uri, LOOKUP_PROJECTION, null, null, null)) {
- if (c == null) {
- Slog.w(TAG, "Null cursor from contacts query.");
- return;
- }
- while (c.moveToNext()) {
- lookupResult.mergeContact(c);
- }
- } catch (Throwable t) {
- Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
- }
- }
-
@VisibleForTesting
protected static class LookupResult {
private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
@@ -619,19 +525,18 @@
}
}
- private class PeopleRankingReconsideration extends RankingReconsideration {
+ @VisibleForTesting
+ class PeopleRankingReconsideration extends RankingReconsideration {
private final LinkedList<String> mPendingLookups;
private final Context mContext;
- // Amount of time to wait for a result from the contacts db before rechecking affinity.
- private static final long LOOKUP_TIME = 1000;
private float mContactAffinity = NONE;
private ArraySet<String> mPhoneNumbers = null;
private NotificationRecord mRecord;
private PeopleRankingReconsideration(Context context, String key,
LinkedList<String> pendingLookups) {
- super(key, LOOKUP_TIME);
+ super(key);
mContext = context;
mPendingLookups = pendingLookups;
}
@@ -642,7 +547,7 @@
long timeStartMs = System.currentTimeMillis();
for (final String handle: mPendingLookups) {
final String cacheKey = getCacheKey(mContext.getUserId(), handle);
- LookupResult lookupResult = null;
+ LookupResult lookupResult;
boolean cacheHit = false;
synchronized (mPeopleCache) {
lookupResult = mPeopleCache.get(cacheKey);
@@ -703,6 +608,102 @@
}
}
+ private static LookupResult resolvePhoneContact(Context context, final String number) {
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number));
+ return searchContacts(context, phoneUri);
+ }
+
+ private static LookupResult resolveEmailContact(Context context, final String email) {
+ Uri numberUri = Uri.withAppendedPath(
+ ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
+ Uri.encode(email));
+ return searchContacts(context, numberUri);
+ }
+
+ @VisibleForTesting
+ static LookupResult searchContacts(Context context, Uri lookupUri) {
+ LookupResult lookupResult = new LookupResult();
+ final Uri corpLookupUri =
+ ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
+ if (corpLookupUri == null) {
+ addContacts(lookupResult, context, lookupUri);
+ } else {
+ addWorkContacts(lookupResult, context, corpLookupUri);
+ }
+ return lookupResult;
+ }
+
+ @VisibleForTesting
+ // Performs a contacts search using searchContacts, and then follows up by looking up
+ // any phone numbers associated with the resulting contact information and merge those
+ // into the lookup result as well. Will have no additional effect if the contact does
+ // not have any phone numbers.
+ static LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
+ LookupResult lookupResult = searchContacts(context, lookupUri);
+ String phoneLookupKey = lookupResult.getPhoneLookupKey();
+ if (phoneLookupKey != null) {
+ String selection = Contacts.LOOKUP_KEY + " = ?";
+ String[] selectionArgs = new String[] { phoneLookupKey };
+ try (Cursor cursor = context.getContentResolver().query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
+ selection, selectionArgs, /* sortOrder= */ null)) {
+ if (cursor == null) {
+ Slog.w(TAG, "Cursor is null when querying contact phone number.");
+ return lookupResult;
+ }
+
+ while (cursor.moveToNext()) {
+ lookupResult.mergePhoneNumber(cursor);
+ }
+ } catch (Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
+ }
+ }
+ return lookupResult;
+ }
+
+ private static void addWorkContacts(LookupResult lookupResult, Context context,
+ Uri corpLookupUri) {
+ final int workUserId = findWorkUserId(context);
+ if (workUserId == -1) {
+ Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
+ return;
+ }
+ final Uri corpLookupUriWithUserId =
+ ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
+ addContacts(lookupResult, context, corpLookupUriWithUserId);
+ }
+
+ /** Returns the user ID of the managed profile or -1 if none is found. */
+ private static int findWorkUserId(Context context) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final int[] profileIds =
+ userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
+ for (int profileId : profileIds) {
+ if (userManager.isManagedProfile(profileId)) {
+ return profileId;
+ }
+ }
+ return -1;
+ }
+
+ /** Modifies the given lookup result to add contacts found at the given URI. */
+ private static void addContacts(LookupResult lookupResult, Context context, Uri uri) {
+ try (Cursor c = context.getContentResolver().query(
+ uri, LOOKUP_PROJECTION, null, null, null)) {
+ if (c == null) {
+ Slog.w(TAG, "Null cursor from contacts query.");
+ return;
+ }
+ while (c.moveToNext()) {
+ lookupResult.mergeContact(c);
+ }
+ } catch (Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ }
+ }
+
@Override
public void applyChangesLocked(NotificationRecord operand) {
float affinityBound = operand.getContactAffinity();
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 eb37302..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.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cde8bd7..fdc2aff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1545,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
@@ -5864,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
@@ -7189,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);
}
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/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/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..324a0ad 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.
@@ -5232,6 +5225,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 +5268,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 +5293,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 +5356,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)) {
@@ -6642,9 +6645,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,20 +7380,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);
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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 211c230..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");
}
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/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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 969f65c..67ca844 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3398,8 +3398,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 4e0f120..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;
}
}
@@ -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() {
@@ -1173,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
@@ -1274,7 +1307,7 @@
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(mToken);
+ mController.finishTransition(this);
}
private void cleanUpInternal() {
@@ -1888,6 +1921,7 @@
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));
@@ -2145,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) {
@@ -2230,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();
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/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..42d23e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -186,7 +186,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;
@@ -7361,14 +7360,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..d6c0311 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -221,8 +221,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 +570,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.
*/
@@ -760,7 +752,6 @@
*/
private InsetsState mFrozenInsetsState;
- private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
/**
@@ -1504,13 +1495,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 +1993,7 @@
boolean isInteresting() {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
- return mActivityRecord != null && !mAppDied
+ return mActivityRecord != null
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE
&& (recentsAnimationController == null
@@ -2448,11 +2432,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));
@@ -2527,21 +2506,6 @@
// 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 +2679,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 +2688,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 +2699,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);
@@ -3084,11 +3025,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 +3040,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 +3886,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 +4192,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());
}
@@ -5407,10 +5320,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/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/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 793d83e..4f8235a 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -168,7 +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 4c5c366..85a48d9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -592,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/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ed139b5..99f3b3e 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -81,6 +81,34 @@
}
/**
+ * A logging utility used primarily for the candidate phase of the current metric setup.
+ *
+ * @param providers a map with known providers
+ * @param emitSequenceId an emitted sequence id for the current session
+ */
+ protected static void logApiCalled(Map<String, ProviderSession> providers,
+ int emitSequenceId) {
+ 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();
+ 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.
@@ -90,6 +118,7 @@
* @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,
@@ -133,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 950cf4f..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,14 +122,7 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- /*
- InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
- TODO immediately once the other change patched through
- mCandidateProviderMetric.setSessionId(initMetric
- .mInitialPhaseMetric.getSessionId());
- mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
- */
- mCandidatePhasePerProviderMetric.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 3ec0fc0..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,14 +248,7 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- /*
- InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
- TODO immediately once the other change patched through
- mCandidateProviderMetric.setSessionId(initMetric
- .mInitialPhaseMetric.getSessionId());
- mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
- */
- mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+ startCandidateMetrics();
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
}
}
@@ -305,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;
@@ -323,6 +340,7 @@
setRemoteEntry(remoteEntry);
}
}
+
public void addCreateEntry(CreateEntry createEntry) {
String id = generateUniqueId();
Entry entry = new Entry(SAVE_ENTRY_KEY,
@@ -373,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 ec8bf22..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;
@@ -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,20 +274,14 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
- /*
- InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
- TODO immediately once the other change patched through
- mCandidateProviderMetric.setSessionId(initMetric
- .mInitialPhaseMetric.getSessionId());
- mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
- */
- mCandidatePhasePerProviderMetric.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: "
@@ -389,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.
@@ -439,11 +439,34 @@
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
return;
}
- // TODO immediately, add to Candidate Phase counts, repeat across all sessions
- // Use sets to dedup type counts
+ 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.
@@ -471,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<>();
@@ -485,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;
@@ -509,6 +534,7 @@
setRemoteEntry(remoteEntry);
}
}
+
public void addCredentialEntry(CredentialEntry credentialEntry) {
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
@@ -559,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 77d4e77..faa91dc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -32,6 +32,7 @@
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;
@@ -72,8 +73,9 @@
@NonNull
protected Boolean mProviderResponseSet = false;
// Specific candidate provider metric for the provider this session handles
- @Nullable
- protected CandidatePhaseMetric mCandidatePhasePerProviderMetric;
+ @NonNull
+ protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+ new CandidatePhaseMetric();
@NonNull
private int mProviderSessionUid;
@@ -143,7 +145,6 @@
mUserId = userId;
mComponentName = componentName;
mRemoteCredentialService = remoteCredentialService;
- mCandidatePhasePerProviderMetric = new CandidatePhaseMetric();
mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
}
@@ -208,6 +209,12 @@
return mRemoteCredentialService;
}
+ 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);
@@ -216,18 +223,37 @@
}
private void updateCandidateMetric(Status status) {
- mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
- // TODO immediately update the candidate phase here to have more new data
- mCandidatePhasePerProviderMetric
- .setQueryFinishTimeNanoseconds(System.nanoTime());
- if (isTerminatingStatus(status)) {
- mCandidatePhasePerProviderMetric.setProviderQueryStatus(
- ProviderStatusForMetrics.QUERY_FAILURE
- .getMetricCode());
- } else if (isCompletionStatus(status)) {
- mCandidatePhasePerProviderMetric.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);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index ed42bb2..3ac10c9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -19,7 +19,6 @@
import static com.android.server.credentials.MetricUtilities.logApiCalled;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
@@ -79,14 +78,14 @@
protected final CancellationSignal mCancellationSignal;
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
- protected InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
- protected ChosenProviderFinalPhaseMetric
+ 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)
- @Nullable
- protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric;
+ @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;
@@ -124,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,
@@ -171,13 +178,17 @@
private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
ProviderSession providerSession) {
- 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());
+ 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) {
@@ -234,6 +245,7 @@
Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
if (isSessionCancelled()) {
+ MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -249,13 +261,8 @@
}
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
- // TODO immediately Add paths to end it (say it fails)
- if (isSessionCancelled()) {
- Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
- // TODO immedaitely Add paths
- } else {
- launchUiWithProviderData(providerDataList);
- }
+ MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+ launchUiWithProviderData(providerDataList);
}
}
@@ -265,22 +272,27 @@
* @param componentName the componentName to associate with a provider
*/
protected void setChosenMetric(ComponentName componentName) {
- CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
- .mCandidatePhasePerProviderMetric;
+ try {
+ CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
+ .mCandidatePhasePerProviderMetric;
- mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
- mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
+ mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
+ mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
- mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
- metric.getQueryLatencyMicroseconds());
+ mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
+ metric.getQueryLatencyMicroseconds());
- mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
- metric.getServiceBeganTimeNanoseconds());
- mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
- metric.getStartQueryTimeNanoseconds());
+ mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+ metric.getServiceBeganTimeNanoseconds());
+ mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+ metric.getStartQueryTimeNanoseconds());
- // TODO immediately update with the entry count numbers from the candidate metrics
+ // 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());
+ 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/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index c392d78..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
@@ -66,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() {
}
@@ -236,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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6cd9f1c..a1789b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3224,7 +3224,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));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index be2873e..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);
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/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/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/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index d72cfc7..0564a73 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.notification;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -51,6 +53,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
+import com.android.server.notification.ValidateNotificationPeople.LookupResult;
+import com.android.server.notification.ValidateNotificationPeople.PeopleRankingReconsideration;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,6 +64,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -215,7 +220,7 @@
ContactsContract.Contacts.CONTENT_LOOKUP_URI,
ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + contactId);
- new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+ PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
verify(mockContentResolver).query(
@@ -242,7 +247,7 @@
final Uri lookupUri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
- new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+ PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
verify(mockContentResolver).query(
@@ -277,7 +282,7 @@
// call searchContacts and then mergePhoneNumbers, make sure we never actually
// query the content resolver for a phone number
- new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+ PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
verify(mockContentResolver, never()).query(
eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -320,7 +325,7 @@
// call searchContacts and then mergePhoneNumbers, and check that we query
// once for the
- new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+ PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
verify(mockContentResolver, times(1)).query(
eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -339,7 +344,7 @@
// Create validator with empty cache
ValidateNotificationPeople vnp = new ValidateNotificationPeople();
- LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
+ LruCache<String, LookupResult> cache = new LruCache<>(5);
vnp.initForTests(mockContext, mockNotificationUsageStats, cache);
NotificationRecord record = getNotificationRecord();
@@ -366,9 +371,8 @@
float affinity = 0.7f;
// Create a fake LookupResult for the data we'll pass in
- LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
- ValidateNotificationPeople.LookupResult lr =
- mock(ValidateNotificationPeople.LookupResult.class);
+ LruCache<String, LookupResult> cache = new LruCache<>(5);
+ LookupResult lr = mock(LookupResult.class);
when(lr.getAffinity()).thenReturn(affinity);
when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel}));
when(lr.isExpired()).thenReturn(false);
@@ -392,6 +396,23 @@
assertTrue(record.getPhoneNumbers().contains(lookupTel));
}
+ @Test
+ public void validatePeople_reconsiderationWillNotBeDelayed() {
+ final Context mockContext = mock(Context.class);
+ final ContentResolver mockContentResolver = mock(ContentResolver.class);
+ when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+ ValidateNotificationPeople vnp = new ValidateNotificationPeople();
+ vnp.initForTests(mockContext, mock(NotificationUsageStats.class), new LruCache<>(5));
+ NotificationRecord record = getNotificationRecord();
+ String[] callNumber = new String[]{"tel:12345678910"};
+ setNotificationPeople(record, callNumber);
+
+ RankingReconsideration rr = vnp.validatePeople(mockContext, record);
+
+ assertThat(rr).isNotNull();
+ assertThat(rr.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(0);
+ }
+
// Creates a cursor that points to one item of Contacts data with the specified
// columns.
private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
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 3045812..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;
@@ -1391,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);
@@ -1400,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);
@@ -1416,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/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 96%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index d3f1091..2442083 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -22,7 +22,7 @@
* Interface for position update and datagram transfer state change callback.
* @hide
*/
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteTransmissionUpdateCallback {
/**
* Called when satellite datagram transfer state 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..d0abfbf 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();
}
}
@@ -742,145 +715,117 @@
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 onDatagramTransferStateChanged(int state,
+ int sendPendingCount, int receivePendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onDatagramTransferStateChanged(
+ state, sendPendingCount, receivePendingCount,
+ errorCode)));
+ }
+
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatellitePositionChanged(pointingInfo)));
+ }
+ };
+ 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 +836,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 +863,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 +889,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 +897,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 +909,7 @@
@Override
public void accept(int result) {
executor.execute(() -> Binder.withCleanCallingIdentity(
- () -> errorCodeListener.accept(result)));
+ () -> resultListener.accept(result)));
}
};
telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
@@ -996,9 +943,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 +979,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 +1074,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 +1106,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 +1128,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 +1138,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 +1147,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 +1175,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 +1183,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 +1209,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 +1358,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 +1373,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 +1388,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/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
similarity index 68%
copy from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
copy to telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d3f1091..0efbd1f 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 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.
@@ -16,13 +16,22 @@
package android.telephony.satellite;
-import android.telephony.satellite.PointingInfo;
+import android.annotation.NonNull;
/**
- * Interface for position update and datagram transfer state change callback.
+ * A callback class for monitoring satellite position update and datagram transfer state change
+ * events.
+ *
* @hide
*/
-oneway interface ISatellitePositionUpdateCallback {
+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 transfer state changed.
*
@@ -31,13 +40,7 @@
* @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);
-
- /**
- * Called when the satellite position changed.
- *
- * @param pointingInfo The pointing info containing the satellite location.
- */
- void onSatellitePositionChanged(in PointingInfo pointingInfo);
+ void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
+ int sendPendingCount, 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/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
new file mode 100644
index 0000000..1eb773d
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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 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();
+ }
}