diff options
475 files changed, 15019 insertions, 5407 deletions
diff --git a/Android.bp b/Android.bp index bc6cda38e0c8..cff863b44499 100644 --- a/Android.bp +++ b/Android.bp @@ -343,6 +343,7 @@ java_defaults { "hardware/interfaces/biometrics/fingerprint/aidl", "hardware/interfaces/graphics/common/aidl", "hardware/interfaces/keymaster/aidl", + "system/hardware/interfaces/media/aidl", ], }, dxflags: [ @@ -632,6 +633,7 @@ stubs_defaults { "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 90b6603b8787..a46ecce5c721 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -422,27 +422,26 @@ java_genrule { "$(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 1d93eb3a211a..0650ce3d141c 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 @@ public class DeviceIdleController extends SystemService } 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService // 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 @@ public class DeviceIdleController extends SystemService // 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService // 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService void setIdleStartTimeForTest(long idleStartTime) { synchronized (this) { mIdleStartTime = idleStartTime; - maybeDoImmediateMaintenance(); + maybeDoImmediateMaintenance("testing"); } } @@ -4224,8 +4223,9 @@ public class DeviceIdleController extends SystemService } @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 @@ public class DeviceIdleController extends SystemService 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 1151bb7d0e6a..26c0eef5542d 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_DAY; 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.BroadcastReceiver; 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.Collections; 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 @@ public class AlarmManagerService extends SystemService { /** * 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 @@ public class AlarmManagerService extends SystemService { | 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 3cc67e7b5677..056b6b913255 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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService "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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService /** * 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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService // 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService /* 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService && 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 e60ed4ade9b7..4c339ac66160 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { 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 537a67039a82..0cc775870f88 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 @@ public final class JobStatus { 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 1ff389deedd2..dffed0f4a190 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 @@ package com.android.server.tare; 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 @@ class InstalledPackageInfo { @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 @@ class InstalledPackageInfo { 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 caf72e828d69..ffb2c03fb02b 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 @@ public class InternalResourceService extends SystemService { 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 @@ public class InternalResourceService extends SystemService { 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 @@ public class InternalResourceService extends SystemService { 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 25d97282035e..9876abb8ce36 100644 --- a/api/api.go +++ b/api/api.go @@ -418,7 +418,6 @@ type bazelCombinedApisAttributes struct { // 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 @@ func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) { "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 c1c44f016dae..02de1cd9a137 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -224,6 +224,7 @@ package android { 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 @@ package android.content { 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 @@ package android.media.tv { 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 @@ package android.os { 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 @@ package android.service.credentials { 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 @@ package android.service.credentials { 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 @@ package android.speech { 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 @@ package android.speech { 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 @@ package android.speech { 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 d573e339c934..0a893f05e91c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -255,7 +255,6 @@ package android { 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 @@ package android.net.wifi.sharedconnectivity.app { 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.trust { 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 @@ package android.service.voice { 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 @@ package android.service.voice { 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 @@ package android.service.voice { 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 0123cc95bc64..445b957a44f3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2027,7 +2027,9 @@ package android.os { 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 87643b23688e..2751b54ebdb7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -849,8 +849,10 @@ public final class ActivityThread extends ClientTransactionHandler @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 class ActivityThread extends ClientTransactionHandler } 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 @@ public final class ActivityThread extends ClientTransactionHandler BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; + s.bindSeq = -1; sendMessage(H.UNBIND_SERVICE, s); } @@ -6653,12 +6657,13 @@ public final class ActivityThread extends ClientTransactionHandler // 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 6301ad7f1278..999075d60e4c 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2561,7 +2561,7 @@ public class ApplicationPackageManager extends PackageManager { 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 4f77203c8c6f..6b5f6b03028e 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -95,7 +95,7 @@ oneway interface IApplicationThread { 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 0ef8e922bf06..09450f59ed3d 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -155,6 +155,14 @@ public class KeyguardManager { "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 440ee202cc5b..e78fb179eb6c 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -8657,13 +8657,13 @@ public class Notification implements Parcelable * 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 e72b14115b18..f7d2afba428e 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS 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 Window* = file:/services/core/java/com/android/server/wm/OWNERS 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 39d77c49eea9..5b955031a098 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -875,15 +875,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } /** - * 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 85daf15865d1..667ec7ecfc5f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5920,9 +5920,8 @@ public class Intent implements Parcelable, Cloneable { /** * 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 8115292e5885..44a84e46113e 100644 --- a/core/java/android/content/RestrictionsManager.java +++ b/core/java/android/content/RestrictionsManager.java @@ -427,11 +427,12 @@ public class RestrictionsManager { * @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 @@ public class RestrictionsManager { * 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 @@ public class RestrictionsManager { * 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 132b9afe2ada..410994d61c2e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -247,7 +247,7 @@ interface IPackageManager { @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 0393d34ba988..f15afdfb7da1 100644 --- a/core/java/android/content/pm/IncrementalStatesInfo.java +++ b/core/java/android/content/pm/IncrementalStatesInfo.java @@ -27,14 +27,18 @@ public class IncrementalStatesInfo implements Parcelable { 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 @@ public class IncrementalStatesInfo implements Parcelable { return mProgress; } + public long getLoadingCompletedTime() { + return mLoadingCompletedTime; + } + @Override public int describeContents() { return 0; @@ -54,6 +62,7 @@ public class IncrementalStatesInfo implements Parcelable { 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 328b0ae96211..b9c671a8f3ea 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8666,7 +8666,7 @@ public abstract class PackageManager { * 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 bf34c1cc5712..a23d7e402768 100644 --- a/core/java/android/credentials/CredentialDescription.java +++ b/core/java/android/credentials/CredentialDescription.java @@ -42,7 +42,7 @@ public final class CredentialDescription implements Parcelable { 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 @@ public final class CredentialDescription implements Parcelable { * 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 final class CredentialDescription implements Parcelable { 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 33418006330c..5e523c0112b1 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -513,6 +513,19 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { * 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 cf20459e4df2..79a551a6be1e 100644 --- a/core/java/android/hardware/CameraSessionStats.java +++ b/core/java/android/hardware/CameraSessionStats.java @@ -54,6 +54,7 @@ public class CameraSessionStats implements Parcelable { 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 @@ public class CameraSessionStats implements Parcelable { mApiLevel = -1; mIsNdk = false; mLatencyMs = -1; + mLogId = 0; mMaxPreviewFps = 0; mSessionType = -1; mInternalReconfigure = -1; @@ -82,7 +84,7 @@ public class CameraSessionStats implements Parcelable { 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 @@ public class CameraSessionStats implements Parcelable { mApiLevel = apiLevel; mIsNdk = isNdk; mLatencyMs = creationDuration; + mLogId = logId; mMaxPreviewFps = maxPreviewFps; mSessionType = sessionType; mInternalReconfigure = internalReconfigure; @@ -127,6 +130,7 @@ public class CameraSessionStats implements Parcelable { 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 @@ public class CameraSessionStats implements Parcelable { 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 @@ public class CameraSessionStats implements Parcelable { 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 b9b310fcc542..c95d081f5f7d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -4123,7 +4123,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 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 722dd08d1ee1..e6b306955ef0 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -125,6 +125,23 @@ public final class CameraManager { "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 @@ public final class CameraManager { } /** + * @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 @@ public final class CameraManager { * 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 @@ public final class CameraManager { * * </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 @@ public final class CameraManager { * 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 @@ public final class CameraManager { * * </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 @@ public final class CameraManager { 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 @@ public final class CameraManager { 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 705afc59f36f..ed2a198dd6ed 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -3645,17 +3645,13 @@ public abstract class CameraMetadata<TKey> { // /** - * <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 381c87d39cac..929868b2faca 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1430,7 +1430,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @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 635e79c01399..a429f30c9a9e 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -849,7 +849,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @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 db83e6218daf..2fa8b877a2df 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -101,15 +101,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes // 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 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } 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 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mInitialized = false; mInitializeHandler = new InitializeSessionHandler(); mSessionId = sessionId; + mInterfaceLock = cameraDevice.mInterfaceLock; } /** @@ -599,13 +601,14 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes 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 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes 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 c2b36560919b..9c878c78855b 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -117,7 +117,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { 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 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { */ @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 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @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 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mSupportedRequestKeys = requestKeys; mSupportedResultKeys = resultKeys; mCaptureResultsSupported = !resultKeys.isEmpty(); + mInterfaceLock = cameraDevice.mInterfaceLock; } private void initializeRepeatingRequestPipeline() throws RemoteException { @@ -969,46 +970,56 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { 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 631df014a580..9743c1f80f9d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1618,15 +1618,20 @@ public class CameraMetadataNative implements Parcelable { } 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 8a40d00327f1..aa55e543dd48 100644 --- a/core/java/android/hardware/input/InputDeviceSensorManager.java +++ b/core/java/android/hardware/input/InputDeviceSensorManager.java @@ -56,7 +56,7 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene // 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene } // 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 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene if (DEBUG) { Slog.d(TAG, "device " + deviceId + " sensor " + sensorType + " disabled"); } - mInputManager.disableSensor(deviceId, sensorType); + mGlobal.disableSensor(deviceId, sensorType); } } } @@ -553,7 +553,7 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene } 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 490589f34411..054ae21be14a 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -44,8 +44,6 @@ import android.os.Handler; 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.VibrationEffect; 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.WindowManager.LayoutParams; 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 @@ public final class InputManager { // 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 @@ public final class InputManager { @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 @@ public final class InputManager { */ @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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 final class InputManager { */ 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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 final class InputManager { */ 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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @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 @@ public final class InputManager { * @hide */ public void unregisterSensorListener(IInputSensorEventListener listener) { - try { - mIm.unregisterSensorListener(listener); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + mGlobal.unregisterSensorListener(listener); } /** @@ -1543,133 +1353,7 @@ public final class InputManager { */ @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 @@ public final class InputManager { */ @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 @@ public final class InputManager { */ @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 final class InputManager { */ 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 final class InputManager { */ 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 @@ public final class InputManager { @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 @@ public final class InputManager { @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 final class InputManager { 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 @@ public final class InputManager { 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 @@ public final class InputManager { 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 82dddfc8756c..524d8206bddf 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 @@ public final class InputManagerGlobal { 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 9689be25ce84..244632a87593 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -64,17 +64,27 @@ public class Build { /** * 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 @@ public class Build { /** 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 @@ public class Build { /** * 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 @@ public class Build { 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 290f929d699a..86e678d9da97 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5601,16 +5601,15 @@ public class UserManager { * * @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 @@ public class UserManager { } /** - * @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 07d265b43ab4..127c7a0476f6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9541,6 +9541,14 @@ public final class Settings { 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 ce8af83a58f8..d0f3820704f7 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1362,6 +1362,11 @@ public class DreamService extends Service implements Window.Callback { 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 644a2bfa70bd..0f3e8d1f4bde 100644 --- a/core/java/android/service/voice/AbstractDetector.java +++ b/core/java/android/service/voice/AbstractDetector.java @@ -20,7 +20,6 @@ import android.annotation.CallSuper; 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 @@ abstract class AbstractDetector implements HotwordDetector { 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 @@ abstract class AbstractDetector implements HotwordDetector { * @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 @@ abstract class AbstractDetector implements HotwordDetector { } } - 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 2830fb7750e4..ffa15f065a02 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -827,28 +827,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { /** * {@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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * * @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { */ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) @Override - public boolean startRecognition() - throws IllegalDetectorStateException { + public boolean startRecognition() { return startRecognition(0); } @@ -1117,44 +1059,28 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * - {@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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * * @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * * @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { * 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @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 93fcec14cf1c..0c8fd48a39d2 100644 --- a/core/java/android/service/voice/HotwordDetector.java +++ b/core/java/android/service/voice/HotwordDetector.java @@ -23,14 +23,10 @@ import android.annotation.NonNull; 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 @@ import java.io.PrintWriter; 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 @@ public interface HotwordDetector { * 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 @@ public interface HotwordDetector { * 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 @@ public interface HotwordDetector { * 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 @@ public interface HotwordDetector { */ 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 767fe378a30c..77900d7bcb5a 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -86,7 +86,7 @@ class SoftwareHotwordDetector extends AbstractDetector { @RequiresPermission(RECORD_AUDIO) @Override - public boolean startRecognition() throws IllegalDetectorStateException { + public boolean startRecognition() { if (DEBUG) { Slog.i(TAG, "#startRecognition"); } @@ -109,7 +109,7 @@ class SoftwareHotwordDetector extends AbstractDetector { /** 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 7dc0687b549d..d7bf07498715 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -84,8 +84,7 @@ public class VisualQueryDetector { * @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 @@ public class VisualQueryDetector { * @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 @@ public class VisualQueryDetector { * @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 @@ public class VisualQueryDetector { } @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 class VisualQueryDetector { 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 3134dcd508bb..1148fe3e43fa 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -78,23 +78,10 @@ oneway interface IRecognitionService { * 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 6c24399acb77..a58ec90c6e2b 100644 --- a/core/java/android/speech/ModelDownloadListener.java +++ b/core/java/android/speech/ModelDownloadListener.java @@ -22,20 +22,27 @@ package android.speech; */ 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 4ecec8fdb582..9656f36d2c4d 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -36,9 +36,7 @@ import android.os.IBinder; 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 @@ public abstract class RecognitionService extends Service { 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 @@ public abstract class RecognitionService extends Service { 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 @@ public abstract class RecognitionService extends Service { 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 @@ public abstract class RecognitionService extends Service { } } - 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 @@ public abstract class RecognitionService extends Service { } /** - * 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. - */ - public void setModelDownloadListener( - @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. + * <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. * - * @param recognizerIntent the request to monitor model download progress for. + * <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 clearModelDownloadListener( + public void onTriggerModelDownload( @NonNull Intent recognizerIntent, - @NonNull AttributionSource attributionSource) { - if (DBG) { - Log.i(TAG, TextUtils.formatSimple( - "#clearModelDownloadListener [%s]", recognizerIntent)); - } + @NonNull AttributionSource attributionSource, + @NonNull ModelDownloadListener listener) { + listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS); } @Override @@ -815,41 +794,18 @@ public abstract class RecognitionService extends Service { @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 76eb09e43e34..dacb25ca1628 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -297,8 +297,6 @@ public class SpeechRecognizer { 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 @@ public class SpeechRecognizer { 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 @@ public class SpeechRecognizer { * 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 @@ public class SpeechRecognizer { // 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 @@ public class SpeechRecognizer { 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 @@ public class SpeechRecognizer { } } - 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 000000000000..9f11e31e4172 --- /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 b4a1e8cfe943..c43864d0f215 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -362,6 +362,15 @@ public class QwertyKeyListener extends BaseKeyListener { 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 2ae882c81b50..6201b3a91eff 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -220,9 +220,9 @@ public class FeatureFlagUtils { 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 8c4e90c81147..c92b1b8c120d 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -195,7 +195,7 @@ public final class Choreographer { 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 @@ public final class Choreographer { mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData = vsyncEventData; + mLastVsyncEventData.copyFrom(vsyncEventData); } AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); @@ -1247,7 +1247,7 @@ public final class Choreographer { 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 @@ public final class Choreographer { 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 b4675e0127de..54db34e788e9 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -146,7 +146,12 @@ public abstract class DisplayEventReceiver { 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 @@ public abstract class DisplayEventReceiver { 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 @@ public abstract class DisplayEventReceiver { 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 a47783cdacfe..6b604422ffba 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -205,6 +205,16 @@ public class HandwritingInitiator { } /** + * 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 720f5692fced..9225cd908c17 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -29,6 +29,7 @@ import android.hardware.SensorManager; 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 @@ public final class InputDevice implements Parcelable { */ @Nullable public static InputDevice getDevice(int id) { - return InputManager.getInstance().getInputDevice(id); + return InputManagerGlobal.getInstance().getInputDevice(id); } /** @@ -750,7 +751,7 @@ public final class InputDevice implements Parcelable { * @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 800fc97d03a6..aec3487910d8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8324,6 +8324,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 c96d298f9382..d80819f29cc6 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; 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 @@ public class ViewConfiguration { } 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 24dcb69f8342..fb25e7a6bfe1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11626,7 +11626,8 @@ public final class ViewRootImpl implements ViewParent, 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 9f9a7815932c..dce54329da8e 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -74,6 +74,7 @@ import android.text.SpannableStringBuilder; 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 @@ public class Editor { 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 @@ public class Editor { 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 @@ public class Editor { 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 @@ public class Editor { 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 1600a16566a5..fd80981fe4b8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -462,12 +462,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 1e6c1ff7a0f0..52e17ca4ab0d 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_SPLIT_TOUCH; 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 @@ public class SnapshotDrawerUtils { 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 0672d6318212..7f99fb7e7815 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; 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 @@ public final class SurfaceSyncGroup { 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 a3209f68acf9..fabb08923089 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -131,6 +131,19 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * 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 57bd3f945358..d521866bccdb 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.annotation.IntRange; 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 @@ public class LocaleHelper { } /** + * 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 685bd9a9b2d2..5dfc0eafd47e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -61,6 +61,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O 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 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O 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 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O return localePicker; } + private void setIsNumberingSystem(boolean isNumberingSystem) { + mIsNumberingSystem = isNumberingSystem; + } + /** * Sets the listener and initializes the locale list. * @@ -184,6 +201,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O 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 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O @Override public void onResume() { super.onResume(); - if (mParentLocale != null) { getActivity().setTitle(mParentLocale.getFullNameNative()); } else { @@ -250,16 +267,28 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O // 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 8b41829154e1..bcbfdc96a18f 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.Locale; 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 @@ public class LocaleStore { 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 @@ public class LocaleStore { .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 @@ public class LocaleStore { } } + String getNumberingSystem() { + return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale); + } + String getContentDescription(boolean countryMode) { if (countryMode) { return getFullCountryNameInUiLanguage(); @@ -383,6 +398,12 @@ public class LocaleStore { 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 @@ public class LocaleStore { 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 @@ public class LocaleStore { 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 class LocaleStore { */ 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 @@ public class LocaleStore { } 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 @@ public class LocaleStore { @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 @@ public class LocaleStore { 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 a61a6d7d241b..08de4dfbe1c4 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -64,6 +64,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { 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 class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { } } + public void setNumberingSystemMode(boolean isNumberSystemMode) { + mIsNumberingMode = isNumberSystemMode; + } + + public boolean getIsForNumberingSystem() { + return mIsNumberingMode; + } + @Override public boolean areAllItemsEnabled() { return false; @@ -209,7 +218,6 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { 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 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { 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 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { 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 bce0d6076d24..f6bcc4661fd6 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_MOD; 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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 @@ public final class DumpUtils { 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] = - ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; - PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] = + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED] = 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 818a50366115..fff778c616ee 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_AVERAGE; 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 @@ public final class ProcessState { 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 @@ public final class ProcessState { 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 @@ public final class ProcessState { 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 @@ public final class ProcessState { 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 @@ public final class ProcessState { 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 @@ public final class ProcessState { 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 f3ed09a861e3..3ce234b4167b 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 final class ProcessStats implements Parcelable { 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 final class ProcessStats implements Parcelable { 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 final class ProcessStats implements Parcelable { }; 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 7ae63b1ac450..928a09700e2e 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -18,6 +18,7 @@ package com.android.internal.jank; 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 @@ public class InteractionJankMonitor { 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 @@ public class InteractionJankMonitor { + ", 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 076e4e118e66..1505ccce97a1 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -185,8 +185,13 @@ public class ZygoteInit { 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 3a393b689717..69d3d6a6d521 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -195,6 +195,8 @@ public class ScreenshotHelper { 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 739055e040af..0c3ff6c28ea7 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -169,21 +169,25 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla 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 611035e1a4ac..70a1354e3046 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 d07ad8986dec..1ad3acd7a3ea 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 e752431ce75f..8eae064cba1f 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 fd787f6ea470..16a8bb7280a4 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 1b3bd2673a7a..76bcc965f28b 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 95ddc2e4ea79..df32d30918c8 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 bef1d0b319b4..3e82bd1814c6 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 b35481d3c31b..97e753e2bdeb 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 5bb86dc4b404..80bf7955c030 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 307490665080..6afdae508623 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 @@ ul.</string> <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 c10612e16acf..1cb56e0c203b 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 1ec2613dd101..fccb177dad4f 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 android.view.stylus.HandwritingTestUtil.createView; 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 @@ public class HandwritingInitiatorTest { } @Test - public void onTouchEvent_startHandwriting_delegate() { + public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() { View delegateView = new View(mContext); delegateView.setIsHandwritingDelegate(true); @@ -245,6 +246,29 @@ public class HandwritingInitiatorTest { } @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 35b3267ea301..61899143b9c5 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.ArgumentMatchers.eq; 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 @@ public class ProcessStatsTest extends TestCase { } @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 2d6ae2ebfb0a..19c52a699183 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 5d303cfb2a85..0faf62e03b14 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -321,6 +321,7 @@ applications that come with the platform <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 8e2a59cd848a..e7ddc88537df 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 fe6eeeb66e40..1048742adb70 100644 --- a/data/keyboards/Generic.kcm +++ b/data/keyboards/Generic.kcm @@ -42,9 +42,10 @@ key C { 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 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' - alt: '\u0301' shift+capslock: 'e' + alt: '\u0301' } key F { @@ -87,8 +88,8 @@ key I { label: 'I' base: 'i' shift, capslock: 'I' - alt: '\u0302' shift+capslock: 'i' + alt: '\u0302' } key J { @@ -123,8 +124,8 @@ key N { label: 'N' base: 'n' shift, capslock: 'N' - alt: '\u0303' shift+capslock: 'n' + alt: '\u0303' } key O { @@ -159,8 +160,8 @@ key S { label: 'S' base: 's' shift, capslock: 'S' - alt: '\u00df' shift+capslock: 's' + alt: '\u00df' } key T { @@ -174,8 +175,8 @@ key U { 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 index 707dfcfe3a10..bfea4db747c2 120000..100644 --- a/data/keyboards/Vendor_004c_Product_0265.idc +++ b/data/keyboards/Vendor_004c_Product_0265.idc @@ -1 +1,34 @@ -Vendor_05ac_Product_0265.idc
\ No newline at end of file +# 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 d72de649c0ed..520d188c71fb 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 53308e312aaa..06b8237c2522 100644 --- a/data/keyboards/Virtual.kcm +++ b/data/keyboards/Virtual.kcm @@ -39,9 +39,10 @@ key C { 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 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' - alt: '\u0301' shift+capslock: 'e' + alt: '\u0301' } key F { @@ -84,8 +85,8 @@ key I { label: 'I' base: 'i' shift, capslock: 'I' - alt: '\u0302' shift+capslock: 'i' + alt: '\u0302' } key J { @@ -120,8 +121,8 @@ key N { label: 'N' base: 'n' shift, capslock: 'N' - alt: '\u0303' shift+capslock: 'n' + alt: '\u0303' } key O { @@ -156,8 +157,8 @@ key S { label: 'S' base: 's' shift, capslock: 'S' - alt: '\u00df' shift+capslock: 's' + alt: '\u00df' } key T { @@ -171,8 +172,8 @@ key U { 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 c1f6c29ca86e..c3b0f9bc16d3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -808,9 +808,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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 ef53839cb2ea..48fe65d3ce59 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 @@ public class BubbleController implements ConfigurationChangeListener { * 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 @@ public class BubbleController implements ConfigurationChangeListener { } } 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 @@ public class BubbleController implements ConfigurationChangeListener { } @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 ecddbda0fff4..e5a4362e5bf0 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 @@ public class BubbleExpandedView extends LinearLayout { 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 4c0a93fb9355..5555bec6a28e 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 @@ public interface Bubbles { * 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 bf226283ae54..cb1a6e7ace6b 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 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.internal.protolog.common.ProtoLog; 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 @@ import java.util.Set; 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 @@ public class TabletopModeController implements } } + /** + * @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 ba0f07376468..7a83d101578f 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.FloatingContentCoordinator; 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 @@ public abstract class WMShellModule { TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, DisplayInsetsController displayInsetsController, + TabletopModeController pipTabletopController, Optional<OneHandedController> oneHandedController, @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable(PipController.create( @@ -366,7 +368,7 @@ public abstract class WMShellModule { 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 73a740381090..31c5e33f21e3 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.WINDOWING_MODE_UNDEFINED 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_OPEN 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( " 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 @@ class DesktopTasksController( " 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 @@ class DesktopTasksController( 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 f6648085075d..f08742db8ebf 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.io.PrintWriter; 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 @@ public class PipBoundsState { * @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 @@ public class PipBoundsState { 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 @@ public class PipBoundsState { @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 db6ef1dffc9c..ea2559af6142 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.ComponentName; 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.ExternalInterfaceBinder; 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.sysui.UserChangeListener; 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, - displayInsetsController, oneHandedController, mainExecutor) + displayInsetsController, pipTabletopController, oneHandedController, mainExecutor) .mImpl; } @@ -444,6 +449,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, DisplayInsetsController displayInsetsController, + TabletopModeController tabletopModeController, Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { @@ -477,6 +483,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb .getInteger(R.integer.config_pipEnterAnimationDuration); mPipParamsChangedForwarder = pipParamsChangedForwarder; mDisplayInsetsController = displayInsetsController; + mTabletopModeController = tabletopModeController; shellInit.addInitCallback(this::onInit, this); } @@ -659,6 +666,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb } }); + 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 a5546e554422..a1eaf851da23 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; - 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 2e864483bf1d..d0948923dc6e 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 @@ package com.android.wm.shell.transition; 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 android.window.TransitionInfo.FLAG_IS_WALLPAPER; 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { /** 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { 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 72da1089c91c..3c0ef965f4f5 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.Context; 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.ShellTaskOrganizer; 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 7a7ac476879e..ddd3b440e1a6 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 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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 0e14c69bdc00..108e273d75a5 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.DisplayController; 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 @@ public class PipControllerTest extends ShellTestCase { @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 @@ public class PipControllerTest extends ShellTestCase { 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 @@ public class PipControllerTest extends ShellTestCase { 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 3901dabcaec8..df78d92a90c8 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 com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P 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 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest - public void testEnterRecents() { + public void testEnterRecentsAndCommit() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() @@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase { .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) + // simulate the start of recents transition + mMainStage.onTaskVanished(mMainChild); + mSideStage.onTaskVanished(mSideChild); + 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.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 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 b0896daee2a1..9df6822b4867 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -91,6 +91,8 @@ bool Properties::isHighEndGfx = true; 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 @@ bool Properties::load() { 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 ed7175e140e4..24e206bbc3b1 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -218,6 +218,8 @@ enum DebugLevel { #define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy" +#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -321,6 +323,8 @@ public: 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 8977d3ce4da3..bfe4eaf39e21 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 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR 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 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR 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 7c56d94d9776..4ed2445da17e 100644 --- a/libs/hwui/effects/GainmapRenderer.h +++ b/libs/hwui/effects/GainmapRenderer.h @@ -30,4 +30,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR 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 75d45e5bd8aa..7eb79be6f55b 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 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j 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 f10b2b2f0694..dd781bb85470 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -311,7 +311,7 @@ float CanvasContext::setColorMode(ColorMode mode) { } 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 24cfc9d70c9b..c3984055a278 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -63,24 +63,23 @@ MouseCursorController::~MouseCursorController() { 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::setPosition(float x, float y) { } 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 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // 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 db0ab56429b2..26be2a858c4e 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -43,12 +43,12 @@ public: 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 @@ private: } 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 fedf58d7c6d0..544edc2a716f 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -114,16 +114,15 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& 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 @@ void PointerController::setPosition(float x, float y) { 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::reloadPointerResources() { } 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 48d5a5756a69..6d3557c89cc7 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -50,12 +50,12 @@ public: ~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 96d83a5f0d15..f6f5d3bc51bd 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -81,7 +81,7 @@ public: 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 c820d0007a4b..2378d42793a1 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -60,7 +60,7 @@ public: 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::loadPointerIconForType(SpriteIcon* ic } 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 23f87abaffed..f86b9af25933 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1566,7 +1566,7 @@ public class ExifInterface { FileInputStream in = null; try { in = new FileInputStream(fileDescriptor); - loadAttributes(in); + loadAttributes(in, fileDescriptor); } finally { closeQuietly(in); if (isFdDuped) { @@ -1637,7 +1637,7 @@ public class ExifInterface { mSeekableFileDescriptor = null; } } - loadAttributes(inputStream); + loadAttributes(inputStream, null); } /** @@ -1963,7 +1963,7 @@ public class ExifInterface { * 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 @@ public class ExifInterface { break; } case IMAGE_TYPE_HEIF: { - getHeifAttributes(inputStream); + getHeifAttributes(inputStream, fd); break; } case IMAGE_TYPE_ORF: { @@ -2580,7 +2580,7 @@ public class ExifInterface { } else if (isSeekableFD(in.getFD())) { mSeekableFileDescriptor = in.getFD(); } - loadAttributes(in); + loadAttributes(in, null); } finally { closeQuietly(in); if (modernFd != null) { @@ -3068,59 +3068,66 @@ public class ExifInterface { } } - 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) { - 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; + @Override + public int readAt(long position, byte[] buffer, int offset, int size) + throws IOException { + if (size == 0) { + return 0; } - - // 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(); + 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; + } - int bytesRead = in.read(buffer, offset, size); - if (bytesRead >= 0) { - mPosition += bytesRead; - return bytesRead; + // 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 } - } catch (IOException e) {} - mPosition = -1; // need to seek on next read - return -1; - } + 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 7d6a8b4c2e97..ef2b5a5b6e50 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -149,10 +149,45 @@ public final class TvInputManager { /** * 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 @@ public final class TvInputManager { * * @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 3c6ed91d2100..4e380c41ccdb 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1030,7 +1030,12 @@ public abstract class TvInputService extends Service { * * @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 @@ public abstract class TvInputService extends Service { * * @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 19a2e5d8d157..8a886832e88e 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -1254,7 +1254,12 @@ public class TvView extends ViewGroup { * @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 7dfe16a3caf9..69ff9c63b32b 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -919,7 +919,12 @@ public abstract class TvInteractiveAppService extends Service { * * @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 1a0319bddc2d..80a14354ef7a 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -944,7 +944,12 @@ public class TvInteractiveAppView extends ViewGroup { * * @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 7f4c03ba090d..6f67d68ed915 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -617,8 +617,6 @@ int64_t MediaEvent::getAudioHandle() { 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 @@ void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size 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 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int siz 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 @@ void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int s 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 @@ void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int siz 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 @@ void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size, 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 @@ void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &eve "Filter object has been freed. Ignoring callback."); } env->DeleteLocalRef(array); - env->DeleteLocalRef(eventClazz); } void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) { @@ -1040,6 +1001,67 @@ void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> 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 @@ FilterClientCallbackImpl::~FilterClientCallbackImpl() { 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 2bb14f65fd82..6b1b6b1a9e71 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -125,6 +125,7 @@ struct MediaEvent : public RefBase { }; struct FilterClientCallbackImpl : public FilterClientCallback { + FilterClientCallbackImpl(); ~FilterClientCallbackImpl(); virtual void onFilterEvent(const vector<DemuxFilterEvent>& events); virtual void onFilterStatus(const DemuxFilterStatus status); @@ -135,6 +136,32 @@ struct FilterClientCallbackImpl : public FilterClientCallback { 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 e91d35bf6979..f4e89a147b9f 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 dd607635b225..56324581c020 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 @@ fun PrimarySelectionCard( 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 16141883df98..9c2064c9b8ea 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm @@ -22,89 +22,89 @@ type OVERLAY 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 EQUALS { 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 BACKSLASH { 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 APOSTROPHE { 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 69490cc343e6..3f5e8944d977 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm @@ -107,72 +107,84 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 3deb9dd14bd4..6751e1d2e826 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm @@ -24,6 +24,7 @@ key GRAVE { label: '\u0401' base: '\u0451' shift, capslock: '\u0401' + shift+capslock: '\u0451' ralt: '`' ralt+shift: '~' } @@ -106,163 +107,203 @@ key Q { 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 @@ key Z { 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 f2c39ce65092..d5293115b896 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm @@ -122,18 +122,21 @@ key A { 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 @@ 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 { @@ -199,60 +209,70 @@ key Q { 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 @@ key W { 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 140c7acc031b..ad3199ff2dc8 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm @@ -115,6 +115,7 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '/' } @@ -122,6 +123,7 @@ key W { label: 'W' base: 'w' shift, capslock: 'W' + shift+capslock: 'w' ralt: '?' } @@ -129,6 +131,7 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' + shift+capslock: 'e' ralt: '\u20ac' } @@ -136,42 +139,49 @@ 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 { @@ -193,60 +203,70 @@ key A { 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 APOSTROPHE { key BACKSLASH { label: ']' base: ']' - shift, capslock: '}' + shift: '}' ralt: '\u00ba' } @@ -274,18 +294,21 @@ key Z { 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 @@ 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: 'n' } key COMMA { diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm index c56367e79467..94ffbd0d10b1 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm @@ -27,7 +27,7 @@ map key 86 PLUS key GRAVE { label: '`' base: '`' - shift, capslock: '~' + shift: '~' ralt: '`' ralt+shift: '~' } @@ -123,89 +123,109 @@ key Q { 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 @@ key RIGHT_BRACKET { base: ';' shift: '\u00a7' ralt: ']' - ralt+shift: '}' + shift+ralt: '}' } ### ROW 3 @@ -222,78 +242,97 @@ key A { 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 @@ key APOSTROPHE { label: '\u0427' base: '\u0447' shift, capslock: '\u0427' + shift+capslock: '\u0447' ralt: '\'' ralt+shift: '"' } @@ -328,62 +368,77 @@ key Z { 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 @@ key PERIOD { label: '\u041b' base: '\u043b' shift, capslock: '\u041b' + shift+capslock: '\u043b' ralt: '.' ralt+shift: '>' } @@ -400,6 +456,7 @@ key SLASH { 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 8878807c55f3..6314158bf1e7 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 @@ key GRAVE { label: '`' base: '\u044e' shift, capslock: '\u042e' + shift+capslock: '\u044e' ralt: '`' ralt+shift: '~' } @@ -122,88 +123,108 @@ key EQUALS { 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 LEFT_BRACKET { key RIGHT_BRACKET { label: '\u0449' base: '\u0449' - shift: '\u0429' + shift, capslock: '\u0429' + shift+capslock: '\u0449' ralt: ']' ralt+shift: '}' } @@ -219,9 +241,8 @@ key RIGHT_BRACKET { 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 @@ key A { 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 SEMICOLON { key APOSTROPHE { label: '\'' base: '\'' - shift, capslock: '"' + shift: '"' ralt: '\'' ralt+shift: '"' } @@ -322,6 +361,7 @@ key PLUS { label: '\u045d' base: '\u045d' shift, capslock: '\u040d' + shift+capslock: '\u045d' ralt: '\\' ralt+shift: '|' } @@ -330,62 +370,76 @@ key Z { 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 COMMA { key PERIOD { label: '.' base: '.' - shift, capslock: '\u201c' + shift: '\u201c' ralt: '.' ralt+shift: '>' } @@ -401,7 +455,7 @@ key PERIOD { 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 96445a42adf2..1c774cce8c16 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 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '\\' } @@ -129,6 +130,7 @@ key W { label: 'W' base: 'w' shift, capslock: 'W' + shift+capslock: 'w' ralt: '|' } @@ -136,6 +138,7 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' + shift+capslock: 'e' ralt: '\u20ac' } @@ -143,48 +146,56 @@ 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 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 @@ key RIGHT_BRACKET { label: '\u0110' base: '\u0111' shift, capslock: '\u0110' + shift+capslock: '\u0111' ralt: '\u00d7' } @@ -201,24 +213,28 @@ key A { 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 @@ key G { label: 'G' base: 'g' shift, capslock: 'G' + shift+capslock: 'g' ralt: ']' } @@ -233,40 +250,48 @@ 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: '\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 @@ key BACKSLASH { label: '\u017d' base: '\u017e' shift, capslock: '\u017d' + shift+capslock: '\u017e' ralt: '\u00a4' } @@ -289,24 +315,28 @@ key Y { 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 @@ key B { label: 'B' base: 'b' shift, capslock: 'B' + shift+capslock: 'b' ralt: '{' } @@ -321,6 +352,7 @@ key N { label: 'N' base: 'n' shift, capslock: 'N' + shift+capslock: 'n' ralt: '}' } @@ -328,6 +360,7 @@ key M { 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 32750e0f36fe..08b012ea130c 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm @@ -131,18 +131,21 @@ key Q { 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 @@ 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: '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 @@ key A { 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 @@ key Z { 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 @@ 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_czech_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm index 457d4da5d211..cad262bc647e 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 @@ key Q { 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 @@ 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 { @@ -211,54 +221,63 @@ key A { 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 @@ key Z { 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 @@ 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_danish.kcm b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm index 9168d1227c2a..83ee8c3939d7 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm @@ -115,76 +115,90 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 6d9c2e59269b..93a508263962 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 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 050b149684aa..da76448ac1d0 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 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 72e6d047479f..e52ccf01be93 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 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 df6a3fde68eb..6ff627b429f8 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 @@ key P { 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 @@ key A { 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 @@ key Q { 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 aa31493e9a02..dff17b3739a9 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 @@ key Q { 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 @@ key T { 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 @@ key A { 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 @@ key D { 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 @@ key Z { 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 @@ 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: '\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 @@ key COMMA { 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 fe82c8d2d3d5..713afba47237 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 @@ key Q { 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 @@ key A { 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 @@ key I { label: 'I' base: 'i' shift, capslock: 'I' + shift+capslock: 'i' } key APOSTROPHE { @@ -277,42 +296,49 @@ key Z { 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 ef545b86bd1c..27a03daf4382 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm @@ -116,18 +116,21 @@ key Q { 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 @@ 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 { 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 @@ key A { 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 @@ key Z { 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 b4deed4506ba..79096ad46147 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm @@ -115,76 +115,90 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 89e83dab372e..490630456bb2 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm @@ -123,18 +123,21 @@ key A { 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 @@ 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 { @@ -199,60 +209,70 @@ key Q { 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 @@ key W { 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 55ddd6096eff..03b5c19f8184 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 @@ key Q { 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 @@ key P { label: 'P' base: 'p' shift, capslock: 'P' + shift+capslock: 'p' ralt: '\u00b6' } @@ -196,54 +206,63 @@ key A { 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 @@ key Z { 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 @@ key SLASH { 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 35b66a37336b..a8f229f70d98 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm @@ -28,6 +28,7 @@ key GRAVE { label: '\u201e' base: '\u201e' shift, capslock: '\u201c' + shift+capslock: '\u201e' ralt: '`' ralt+shift: '~' } @@ -128,79 +129,92 @@ key Q { 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 LEFT_BRACKET { key RIGHT_BRACKET { label: ']' base: ']' - shift, capslock: '}' + shift: '}' ralt: ']' ralt+shift: '}' } @@ -227,72 +241,84 @@ key A { 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 SEMICOLON { key APOSTROPHE { label: '\'' base: '\'' - shift, capslock: '"' + shift: '"' ralt: '\'' ralt+shift: '"' } @@ -311,57 +337,66 @@ key Z { 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 COMMA { 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 d9caa32c81fe..23ccc9aa6b17 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 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '@' } @@ -124,12 +125,14 @@ 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' } @@ -137,48 +140,56 @@ 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 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 @@ key A { 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 @@ key Y { 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 a7684e12766d..6eff11437667 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm @@ -24,88 +24,88 @@ map key 86 PLUS 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 EQUALS { key Q { label: 'Q' - base, capslock: ';' + base: ';' shift: ':' } key W { label: 'W' - base, capslock: '\u03c2' + base: '\u03c2' shift: '\u0385' } @@ -128,6 +128,7 @@ key E { label: 'E' base: '\u03b5' shift, capslock: '\u0395' + shift+capslock: '\u03b5' ralt: '\u20ac' } @@ -135,6 +136,7 @@ key R { label: 'R' base: '\u03c1' shift, capslock: '\u03a1' + shift+capslock: '\u03c1' ralt: '\u00ae' } @@ -142,12 +144,14 @@ key T { 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 @@ key U { 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 @@ key A { 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 SEMICOLON { key APOSTROPHE { label: '\'' - base, capslock: '\'' + base: '\'' shift: '"' } key BACKSLASH { label: '\\' - base, capslock: '\\' + base: '\\' shift: '|' ralt: '\u00ac' } @@ -270,7 +287,7 @@ key BACKSLASH { key PLUS { label: '<' - base, capslock: '<' + base: '<' shift: '>' ralt: '\\' shift+ralt: '|' @@ -280,18 +297,21 @@ key Z { 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 @@ key V { 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 283cb4ef2081..11ade4238d19 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm @@ -121,18 +121,21 @@ key Q { 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 @@ key R { 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 @@ key I { 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 @@ key A { 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 @@ key J { label: 'J' base: '\u05d7' shift, capslock: 'J' + shift+capslock: 'j' ralt: '\u05f1' } @@ -241,12 +258,14 @@ key K { 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 @@ key SEMICOLON { base: '\u05e3' shift: ':' capslock: ';' + shift+capslock: ':' } key APOSTROPHE { @@ -261,6 +281,7 @@ key APOSTROPHE { base: ',' shift: '"' capslock: '\'' + shift+capslock: '"' } key BACKSLASH { @@ -273,7 +294,7 @@ key BACKSLASH { key PLUS { label: '\\' - base, capslock: '\\' + base: '\\' shift: '|' } @@ -281,42 +302,49 @@ key Z { 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 @@ key COMMA { base: '\u05ea' shift: '>' capslock: ',' + shift+capslock: '>' } key PERIOD { @@ -331,6 +360,7 @@ key PERIOD { base: '\u05e5' shift: '<' capslock: '.' + shift+capslock: '<' } key SLASH { @@ -338,4 +368,5 @@ key SLASH { 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 dafb50ba0f8f..6c947c77ad3d 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm @@ -101,6 +101,7 @@ key GRAVE { label: '\u00d6' base: '\u00f6' shift, capslock: '\u00d6' + shift+capslock: '\u00f6' ralt: '\u030b' } @@ -108,6 +109,7 @@ key SLASH { label: '\u00dc' base: '\u00fc' shift, capslock: '\u00dc' + shift+capslock: '\u00fc' ralt: '\u0308' } @@ -115,6 +117,7 @@ key EQUALS { label: '\u00d3' base: '\u00f3' shift, capslock: '\u00d3' + shift+capslock: '\u00f3' ralt: '\u0327' } @@ -124,6 +127,7 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '\\' } @@ -131,6 +135,7 @@ key W { label: 'W' base: 'w' shift, capslock: 'W' + shift+capslock: 'w' ralt: '|' } @@ -138,6 +143,7 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' + shift+capslock: 'e' ralt: '\u00c4' } @@ -145,24 +151,28 @@ 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 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 @@ key I { label: 'I' base: 'i' shift, capslock: 'I' + shift+capslock: 'i' ralt: '\u00cd' } @@ -177,18 +188,21 @@ 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: '\u0150' base: '\u0151' shift, capslock: '\u0150' + shift+capslock: '\u0151' ralt: '\u00f7' } @@ -196,6 +210,7 @@ key RIGHT_BRACKET { label: '\u00da' base: '\u00fa' shift, capslock: '\u00da' + shift+capslock: '\u00fa' ralt: '\u00d7' } @@ -205,6 +220,7 @@ key A { label: 'A' base: 'a' shift, capslock: 'A' + shift+capslock: 'a' ralt: '\u00e4' } @@ -212,6 +228,7 @@ key S { label: 'S' base: 's' shift, capslock: 'S' + shift+capslock: 's' ralt: '\u0111' } @@ -219,6 +236,7 @@ key D { label: 'D' base: 'd' shift, capslock: 'D' + shift+capslock: 'd' ralt: '\u0110' } @@ -226,6 +244,7 @@ key F { label: 'F' base: 'f' shift, capslock: 'F' + shift+capslock: 'f' ralt: '[' } @@ -233,6 +252,7 @@ key G { label: 'G' base: 'g' shift, capslock: 'G' + shift+capslock: 'g' ralt: ']' } @@ -240,12 +260,14 @@ key H { 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 @@ key K { label: 'K' base: 'k' shift, capslock: 'K' + shift+capslock: 'k' ralt: '\u0197' } @@ -260,6 +283,7 @@ key L { label: 'L' base: 'l' shift, capslock: 'L' + shift+capslock: 'l' ralt: '\u0141' } @@ -267,6 +291,7 @@ key SEMICOLON { label: '\u00c9' base: '\u00e9' shift, capslock: '\u00c9' + shift+capslock: '\u00e9' ralt: '$' } @@ -274,6 +299,7 @@ key APOSTROPHE { label: '\u00c1' base: '\u00e1' shift, capslock: '\u00c1' + shift+capslock: '\u00e1' ralt: '\u00df' } @@ -281,6 +307,7 @@ key BACKSLASH { label: '\u0170' base: '\u0171' shift, capslock: '\u0170' + shift+capslock: '\u0171' ralt: '\u00a4' } @@ -290,6 +317,7 @@ key PLUS { label: '\u00cd' base: '\u00ed' shift, capslock: '\u00cd' + shift+capslock: '\u00ed' ralt: '<' } @@ -297,6 +325,7 @@ key Y { label: 'Y' base: 'y' shift, capslock: 'Y' + shift+capslock: 'y' ralt: '>' } @@ -304,6 +333,7 @@ key X { label: 'X' base: 'x' shift, capslock: 'X' + shift+capslock: 'x' ralt: '#' } @@ -311,6 +341,7 @@ key C { label: 'C' base: 'c' shift, capslock: 'C' + shift+capslock: 'c' ralt: '&' } @@ -318,6 +349,7 @@ key V { label: 'V' base: 'v' shift, capslock: 'V' + shift+capslock: 'v' ralt: '@' } @@ -325,6 +357,7 @@ key B { label: 'B' base: 'b' shift, capslock: 'B' + shift+capslock: 'b' ralt: '{' } @@ -332,6 +365,7 @@ key N { label: 'N' base: 'n' shift, capslock: 'N' + shift+capslock: 'n' ralt: '}' } @@ -339,6 +373,7 @@ key M { 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 117f58bf6f5a..5131b4f513b2 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm @@ -99,6 +99,7 @@ key EQUALS { label: '\u00d6' base: '\u00f6' shift, capslock: '\u00d6' + shift+capslock: '\u00f6' ralt: '\\' } @@ -114,6 +115,7 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '@' } @@ -121,12 +123,14 @@ 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,48 +138,56 @@ 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 { label: '\u0110' base: '\u0111' shift, capslock: '\u0110' + shift+capslock: '\u0111' } key RIGHT_BRACKET { @@ -191,60 +203,70 @@ key A { 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 @@ key Z { 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 @@ key SLASH { 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 bd2d25a41a30..309d8b2149b0 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm @@ -109,18 +109,21 @@ key Q { 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 @@ 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 { @@ -188,54 +198,63 @@ key A { 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 @@ key Z { 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 d4bc0c03f8fb..3b77cb1f030e 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 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 72ca33381525..bcfdb12f21bf 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm @@ -32,6 +32,7 @@ key 1 { label: '1' base: '\u0105' shift, capslock: '\u0104' + shift+capslock: '\u0105' ralt: '1' shift+ralt: '!' } @@ -40,6 +41,7 @@ key 2 { label: '2' base: '\u010d' shift, capslock: '\u010c' + shift+capslock: '\u010d' ralt: '2' shift+ralt: '@' } @@ -48,6 +50,7 @@ key 3 { label: '3' base: '\u0119' shift, capslock: '\u0118' + shift+capslock: '\u0119' ralt: '3' shift+ralt: '#' } @@ -56,6 +59,7 @@ key 4 { label: '4' base: '\u0117' shift, capslock: '\u0116' + shift+capslock: '\u0117' ralt: '4' shift+ralt: '$' } @@ -64,6 +68,7 @@ key 5 { label: '5' base: '\u012f' shift, capslock: '\u012e' + shift+capslock: '\u012f' ralt: '5' shift+ralt: '%' } @@ -72,6 +77,7 @@ key 6 { label: '6' base: '\u0161' shift, capslock: '\u0160' + shift+capslock: '\u0161' ralt: '6' shift+ralt: '\u0302' } @@ -80,6 +86,7 @@ key 7 { label: '7' base: '\u0173' shift, capslock: '\u0172' + shift+capslock: '\u0173' ralt: '7' shift+ralt: '&' } @@ -88,6 +95,7 @@ key 8 { label: '8' base: '\u016b' shift, capslock: '\u016a' + shift+capslock: '\u016b' ralt: '8' shift+ralt: '*' } @@ -116,6 +124,7 @@ key EQUALS { label: '=' base: '\u017e' shift, capslock: '\u017d' + shift+capslock: '\u017e' ralt: '=' shift+ralt: '+' } @@ -126,18 +135,21 @@ key Q { 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 @@ 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 { @@ -201,54 +220,63 @@ key A { 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 @@ key Z { 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 3d4a8c69d216..77cc672f45b5 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm @@ -28,6 +28,7 @@ key GRAVE { label: '=' base: '=' shift, capslock: '+' + shift+capslock: '+' ralt: '`' ralt+shift: '~' } @@ -122,86 +123,107 @@ key Q { 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 @@ key RIGHT_BRACKET { label: '\u044a' base: '\u044a' shift, capslock: '\u042a' + shift+capslock: '\u044a' ralt: ']' ralt+shift: '}' } @@ -220,78 +243,97 @@ key A { 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 @@ key APOSTROPHE { label: '\u043f' base: '\u043f' shift, capslock: '\u041f' + shift+capslock: '\u043f' ralt: '\'' ralt+shift: '"' } @@ -318,62 +361,77 @@ key Z { 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 @@ key PERIOD { 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 560dd1631add..cae1c9411f3e 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm @@ -115,76 +115,90 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 bfe78212b1eb..67449220b189 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm @@ -22,231 +22,222 @@ key A { 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 @@ key 7 { 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 559ec07705b2..66fbefc1757d 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm @@ -104,64 +104,76 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 47ee86708d18..6fe0e47c2d79 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm @@ -115,18 +115,21 @@ key Q { 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 @@ 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 { @@ -191,60 +201,70 @@ key A { 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 @@ key Z { 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 41c6bb3644be..ecada49a49bc 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm @@ -28,6 +28,7 @@ key GRAVE { label: '\u0401' base: '\u0451' shift, capslock: '\u0401' + shift+capslock: '\u0451' ralt: '`' ralt+shift: '~' } @@ -124,86 +125,107 @@ key Q { 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 @@ key RIGHT_BRACKET { label: '\u042a' base: '\u044a' shift, capslock: '\u042a' + shift+capslock: '\u044a' ralt: ']' ralt+shift: '}' } @@ -222,78 +245,97 @@ 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: ':' } @@ -302,6 +344,7 @@ key APOSTROPHE { label: '\u042d' base: '\u044d' shift, capslock: '\u042d' + shift+capslock: '\u044d' ralt: '\'' ralt+shift: '"' } @@ -319,62 +362,77 @@ key Z { 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 @@ key PERIOD { 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 11c2ad449bac..5417bc380622 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 @@ key Q { 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 @@ key RIGHT_BRACKET { label: '\u042a' base: '\u044a' shift, capslock: '\u042a' + shift+capslock: '\u044a' ralt: ']' ralt+shift: '}' } @@ -224,78 +246,97 @@ 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: ':' } @@ -304,6 +345,7 @@ key APOSTROPHE { label: '\u042d' base: '\u044d' shift, capslock: '\u042d' + shift+capslock: '\u044d' ralt: '\'' ralt+shift: '"' } @@ -312,6 +354,7 @@ key BACKSLASH { label: '\u0401' base: '\u0451' shift, capslock: '\u0401' + shift+capslock: '\u0451' ralt: '\\' ralt+shift: '|' } @@ -330,62 +373,77 @@ key Z { 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 @@ key PERIOD { 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 2eb0f637b096..5065aa87ca66 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm @@ -118,6 +118,7 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '\\' } @@ -125,6 +126,7 @@ key W { label: 'W' base: 'w' shift, capslock: 'W' + shift+capslock: 'w' ralt: '|' } @@ -132,6 +134,7 @@ key E { label: 'E' base: 'e' shift, capslock: 'E' + shift+capslock: 'e' ralt: '\u20ac' } @@ -139,42 +142,49 @@ 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' ralt: '\'' } @@ -198,12 +208,14 @@ key A { 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 @@ key D { label: 'D' base: 'd' shift, capslock: 'D' + shift+capslock: 'd' ralt: '\u0110' } @@ -218,6 +231,7 @@ key F { label: 'F' base: 'f' shift, capslock: 'F' + shift+capslock: 'f' ralt: '[' } @@ -225,6 +239,7 @@ key G { label: 'G' base: 'g' shift, capslock: 'G' + shift+capslock: 'g' ralt: ']' } @@ -232,18 +247,21 @@ 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: '\u0142' } @@ -251,6 +269,7 @@ key L { label: 'L' base: 'l' shift, capslock: 'L' + shift+capslock: 'l' ralt: '\u0141' } @@ -288,6 +307,7 @@ key Z { label: 'Z' base: 'z' shift, capslock: 'Z' + shift+capslock: 'z' ralt: '>' } @@ -295,6 +315,7 @@ key X { label: 'X' base: 'x' shift, capslock: 'X' + shift+capslock: 'x' ralt: '#' } @@ -302,6 +323,7 @@ key C { label: 'C' base: 'c' shift, capslock: 'C' + shift+capslock: 'c' ralt: '&' } @@ -309,6 +331,7 @@ key V { label: 'V' base: 'v' shift, capslock: 'V' + shift+capslock: 'v' ralt: '@' } @@ -316,6 +339,7 @@ key B { label: 'B' base: 'b' shift, capslock: 'B' + shift+capslock: 'b' ralt: '{' } @@ -323,6 +347,7 @@ key N { label: 'N' base: 'n' shift, capslock: 'N' + shift+capslock: 'n' ralt: '}' } @@ -330,6 +355,7 @@ key M { 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 da9159b89bf8..6a63e709ec15 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm @@ -113,18 +113,21 @@ key Q { 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 @@ 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 { @@ -190,60 +200,70 @@ key A { 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 @@ key BACKSLASH { label: '\u00c7' base: '\u00e7' shift, capslock: '\u00c7' + shift+capslock: '\u00e7' ralt: '}' } @@ -272,42 +293,49 @@ key Z { 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 16eb53f29408..29aab97fed5c 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 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '@' } @@ -116,54 +117,63 @@ 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,60 +196,70 @@ key A { 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 @@ key Z { 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 8a4e9a505dfa..f12804ff62ab 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm @@ -115,76 +115,90 @@ key Q { 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 @@ key A { 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 @@ key Z { 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 9e204624b82d..6476793caf16 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 @@ key Q { 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 @@ 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 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 @@ key A { 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 @@ key Y { 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 7fbd1a9b2828..9d6f3675ecc3 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 @@ key Q { 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 @@ 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 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 @@ key A { 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 @@ key Y { 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 e193d341caf5..2a8fcef73ea0 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm @@ -124,6 +124,7 @@ key Q { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '@' } @@ -131,12 +132,14 @@ 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' } @@ -144,50 +147,59 @@ 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: '\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 @@ key RIGHT_BRACKET { label: '\u00dc' base: '\u00fc' shift, capslock: '\u00dc' + shift+capslock: '\u00fc' ralt: '\u0303' } @@ -204,14 +217,17 @@ key A { 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 @@ 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: '\u015e' base: '\u015f' shift, capslock: '\u015e' + shift+capslock: '\u015f' ralt: '\u0301' } @@ -268,6 +292,7 @@ key APOSTROPHE { label: '\u0130' base: 'i' shift, capslock: '\u0130' + shift+capslock: 'i' } key COMMA { @@ -290,54 +315,63 @@ key Z { 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 5b96da027be7..b27f6fa3b1aa 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 @@ key Q { label: 'F' base: 'f' shift, capslock: 'F' + shift+capslock: 'f' ralt: '@' } @@ -132,32 +133,38 @@ key W { 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 @@ key U { 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 @@ key LEFT_BRACKET { label: 'Q' base: 'q' shift, capslock: 'Q' + shift+capslock: 'q' ralt: '"' } @@ -199,6 +212,7 @@ key RIGHT_BRACKET { label: 'W' base: 'w' shift, capslock: 'W' + shift+capslock: 'w' ralt: '~' } @@ -208,22 +222,27 @@ key A { 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 @@ key F { label: 'A' base: 'a' shift, capslock: 'A' + shift+capslock: 'a' ralt: '\u00aa' } @@ -238,12 +258,14 @@ key G { 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 @@ key J { 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 @@ key APOSTROPHE { label: '\u015f' base: '\u015f' shift, capslock: '\u015e' + shift+capslock: '\u015f' } key COMMA { @@ -292,63 +319,76 @@ key PLUS { 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 @@ key BACKSLASH { 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 a8024603555e..1346bbb489c1 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm @@ -28,6 +28,7 @@ key GRAVE { label: '\u0401' base: '\u0451' shift, capslock: '\u0401' + shift+capslock: '\u0451' ralt: '`' ralt+shift: '~' } @@ -124,86 +125,107 @@ key Q { 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 @@ key RIGHT_BRACKET { label: '\u0407' base: '\u0457' shift, capslock: '\u0407' + shift+capslock: '\u0457' ralt: ']' ralt+shift: '}' } @@ -222,78 +245,97 @@ 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: '\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 @@ key APOSTROPHE { label: '\u0404' base: '\u0454' shift, capslock: '\u0404' + shift+capslock: '\u0454' ralt: '\'' ralt+shift: '"' } @@ -319,6 +362,7 @@ key PLUS { label: '\u0490' base: '\u0491' shift, capslock: '\u0490' + shift+capslock: '\u0491' ralt: '\\' ralt+shift: '|' } @@ -327,62 +371,77 @@ key Z { 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 @@ key PERIOD { 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 bd9e760acfda..c8bcabff1094 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 android.util.Slog; 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 @@ public class ServiceListing { 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 @@ public class ServiceListing { mPermission = permission; mNoun = noun; mAddDeviceLockedFlags = addDeviceLockedFlags; + mValidator = validator; } public void addCallback(Callback callback) { @@ -137,7 +140,6 @@ public class ServiceListing { 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 @@ public class ServiceListing { + mPermission); continue; } + if (mValidator != null && !mValidator.test(info)) { + continue; + } mServices.add(info); } for (Callback callback : mCallbacks) { @@ -194,6 +199,7 @@ public class ServiceListing { private String mPermission; private String mNoun; private boolean mAddDeviceLockedFlags = false; + private Predicate mValidator; public Builder(Context context) { mContext = context; @@ -224,6 +230,11 @@ public class ServiceListing { 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 class ServiceListing { 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 688fc720d058..c4f09cecfa1f 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.os.ServiceManager; 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 @@ public class DreamBackend { 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 @@ public class DreamBackend { /** 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 @@ public class DreamBackend { 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 @@ public class DreamBackend { 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 684a9aaf36aa..c9e831256cf4 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_DEVICE; 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 @@ public class InfoMediaManager extends MediaManager { 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 6b9866bf05e0..071ab27f60b9 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.MediaRoute2Info.TYPE_WIRED_HEADSET; 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_TRACK_UNSUPPORTE 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 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { 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 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * * @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 f7fd25b9fb7d..7ff0988c494d 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 @@ package com.android.settingslib.applications; 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,16 +54,97 @@ public class ServiceListingTest { 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") + .setIntentAction(TEST_INTENT) + .setPermission("testPermission") + .build(); + } + + @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 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 52b9227fb373..22ec12d44d6d 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.annotation.Config; 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 @@ public final class DreamBackendTest { @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 f63c06adbea2..270fda89c212 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 com.google.common.truth.Truth.assertThat; 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 @@ public class InfoMediaManagerTest { } @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 09a1ba2b1590..e50f52229a16 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 @@ public class GlobalSettings { 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 f66fcba74bc5..3efb41dbfe5c 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 @@ public class SecureSettings { 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 e57cf3b4cf22..8d07fb67c14f 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_FORCE; 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 @@ public class GlobalSettingsValidators { 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 558e19f19986..abd2c7511567 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 @@ public class SecureSettingsValidators { 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 db6cc1a39ef1..a8eeec3c2f24 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 @@ package com.android.providers.settings; 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 @@ import java.io.IOException; /** * 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 @@ final class GenerationRegistry { } 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 @@ final class GenerationRegistry { * 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 @@ final class GenerationRegistry { // 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 @@ final class GenerationRegistry { 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 @@ final class GenerationRegistry { + " 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 e6408bf988ce..7607909dc40b 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 android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OV 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 @@ public class SettingsProvider extends ContentProvider { @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 @@ public class SettingsProvider extends ContentProvider { } } - 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 4d8705f135af..e3153e046a96 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 @@ final class SettingsState { } } + 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 9d2352670ddd..0bf53ccf81a6 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 278ceb944ef6..1f14723f466f 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -372,7 +372,6 @@ public class SettingsBackupTest { 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 6ec8146baee0..586d6f73baad 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 @@ public class GenerationRegistryTest { 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 1136c11e9a54..4290ca0d0982 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 440c6e58d581..ca8426560ccc 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 000000000000..78ae4af258fc --- /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 7fe94d349c42..9e9929e79d47 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.animation.ValueAnimator 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 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit * * // 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 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { 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 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { * 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 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { * 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 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { 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 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { invalidateCallback() } } -} -private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V { - var v = get(key) - if (v == null) { - v = defaultValue() - put(key, v) + /** + * 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, + ) } - 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 459a38e9318c..09762b04e6a9 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.JavaContext 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 @@ class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner { 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 @@ class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner { } } + 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 @@ class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner { 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 63eb2632979c..a1e6f92a7218 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.TestFile 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 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expectClean() @@ -65,6 +69,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expectClean() @@ -88,6 +93,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expect( @@ -115,6 +121,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expect( @@ -145,6 +152,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expectClean() @@ -168,6 +176,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expect( @@ -198,6 +207,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expectClean() @@ -221,6 +231,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expect( @@ -248,6 +259,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { .indented(), *stubs ) + .customScope(testScope) .issues(DemotingTestWithoutBugDetector.ISSUE) .run() .expect( @@ -260,6 +272,7 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() { ) } + 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 000000000000..946e77959b1c --- /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 000000000000..c1defb722077 --- /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 e253fb925ceb..cc337459a83c 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.content.Context 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 @@ object ComposeFacade : BaseComposeFacade { 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 1ea18fec4abe..0e79b18b1c24 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.activity.compose.setContent 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 @@ object ComposeFacade : BaseComposeFacade { 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 772c8918fd2d..fbd7f83ad350 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 @@ internal object ComposeInitializerImpl : ComposeInitializer { // 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 000000000000..b9e38cf3cc60 --- /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 000000000000..98ef57f94783 --- /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 000000000000..ca91b8a21a81 --- /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 000000000000..665d6dd0cfa2 --- /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 000000000000..f514ab4a710b --- /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 70b5d739ea7c..b7088d5d3d23 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 @@ public interface StatusBarStateController { 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 7a628bb65433..642582c755ac 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 9d914197c05a..85b6e8dc12b3 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 000000000000..78ff5f03bea2 --- /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 4abc1769ab54..fe9542b7aed6 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 311990c45d43..e5cd0c55027b 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 a02c429e0229..ebf0f8ecd48c 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 0e5f8c1c7a26..553453d7f79d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -18,13 +18,9 @@ package com.android.keyguard; 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 androidx.annotation.Nullable; 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 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug - mHandler = new Handler(Looper.myLooper()); onThemeChanged(); } @@ -127,9 +110,6 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe 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 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe /** 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 6a9216218d07..c1896fc641e0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -18,11 +18,17 @@ package com.android.keyguard; 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 @@ import javax.inject.Inject; */ 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 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; + mAnnounceRunnable = new AnnounceRunnable(mView); } @Override @@ -100,6 +113,12 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> */ 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 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> 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 98866c694526..7255383049da 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -1066,23 +1066,28 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } 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 39b567fd21b9..68e1dd7d8eab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -19,11 +19,16 @@ package com.android.keyguard; 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 @@ public class KeyguardSecurityViewFlipperController 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 @@ public class KeyguardSecurityViewFlipperController } } - 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 @@ public class KeyguardSecurityViewFlipperController 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 @@ public class KeyguardSecurityViewFlipperController 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 9c847be75fab..08236b7e280a 100644 --- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags +++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags @@ -61,3 +61,8 @@ option java_package com.android.systemui; ## 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 c31d45ff0b83..4aa985b50967 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -198,31 +198,35 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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); + closeDioalog(reason); + } + } + }; - mCurrentDialog.dismissWithoutCallback(true /* animate */); - mCurrentDialog = null; + 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(); - } + 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); + 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(); @@ -546,6 +550,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } + @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 c0f854958c41..4173bdc3c261 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.content.Context 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 @@ interface BaseComposeFacade { 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 06d4a0888197..ce0f2e93f26d 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 @@ internal constructor( 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 @@ internal constructor( 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 @@ internal constructor( .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 bf0a69296dfd..224eb1ca409a 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.BroadcastReceiver 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 @@ class ControlsActivity @Inject constructor( 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 @@ class ControlsActivity @Inject constructor( 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 0d5311752ab9..3ecf4236656d 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 @@ interface ControlsUiController { * 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 5da86de933e6..ee12db8d07b1 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 @@ class ControlsUiControllerImpl @Inject constructor ( } 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 @@ class ControlsUiControllerImpl @Inject constructor ( this.overflowMenuAdapter = null hidden = false retainCache = false + selectionItem = null controlActionCoordinator.activityContext = activityContext @@ -272,7 +274,7 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun reload(parent: ViewGroup) { + private fun reload(parent: ViewGroup, dismissTaskView: Boolean = true) { if (hidden) return controlsListingController.get().removeCallback(listingCallback) @@ -327,8 +329,8 @@ class ControlsUiControllerImpl @Inject constructor ( @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 @@ class ControlsUiControllerImpl @Inject constructor ( } 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 @@ class ControlsUiControllerImpl @Inject constructor ( 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 @@ class ControlsUiControllerImpl @Inject constructor ( } } + 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 78e87cafc4f2..1f89c917186a 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 @@ class PanelTaskViewController( 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 @@ class PanelTaskViewController( } } + 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 b054c7ee2eaf..0be3bb69d136 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.accessibility.CaptioningManager; 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 @@ public class FrameworkServicesModule { 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 244212b45790..1702eac6d02a 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 @@ public class ComplicationTypesUpdater extends ConditionalCoreStartable { 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 9b954f5f0c4a..616bd81abe4d 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.R; 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 @@ public interface RegisteredComplicationsModule { 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 63f63a5093d2..78e132ff6397 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.dagger.qualifiers.Main 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 @@ class DreamSmartspaceController @Inject constructor( @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 @@ class DreamSmartspaceController @Inject constructor( 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 @@ class DreamSmartspaceController @Inject constructor( } private fun connectSession() { - if (plugin == null || session != null || !hasActiveSessionListeners()) { + if (plugin == null && weatherPlugin == null) { + return + } + if (session != null || !hasActiveSessionListeners()) { return } @@ -166,13 +196,14 @@ class DreamSmartspaceController @Inject constructor( } 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 @@ class DreamSmartspaceController @Inject constructor( 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 5c310c317b99..2c11d78f534b 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 @@ public class FeatureFlagsDebug implements FeatureFlags { 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 9859ff6b4917..9d19a7dc4604 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 @@ public class FeatureFlagsRelease implements FeatureFlags { 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 @@ public class FeatureFlagsRelease implements FeatureFlags { @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 b611f468cf1c..661b2b6daf64 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 @@ object Flags { @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 @@ object Flags { // 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 9b748d0a0eb2..eaf5eac155ef 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 @@ interface ServerFlagReader { fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener) interface ChangeListener { - fun onChange(flag: Flag<*>) + fun onChange(flag: Flag<*>, value: String?) } } @@ -67,7 +67,7 @@ class ServerFlagReaderImpl @Inject constructor( 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 @@ class ServerFlagReaderFake : ServerFlagReader { 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 @@ class ServerFlagReaderFake : ServerFlagReader { 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 377a136920f0..5ecc00fb66b5 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.keyguard.mediator.ScreenOnCoordinator; 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 450fa1420408..82be009a7560 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 @@ public class WorkLockActivity extends Activity { 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 @@ public class WorkLockActivity extends Activity { 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 d7167845419b..5fcf1052d949 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.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition 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 @@ object KeyguardBouncerViewBinder { 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 68910c65e508..0656c9baa921 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 @@ constructor( /** 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 d246b35ea397..889adc77196a 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 @@ public class LogModule { 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 000000000000..3a639a0cab61 --- /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 a31c1e566018..00e5aacdedae 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 @@ public class MediaControlPanel { 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 00e9a79a3213..b71a91934314 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { && 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 2a2cf63464ce..f76f049abf97 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.animation.ValueAnimator; 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 2250d72d8658..39d4e6e8d68a 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 @@ public class MediaOutputMetricLogger { Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType); } + if (mSourceDevice == null && mTargetDevice == null) { + return; + } + updateLoggingDeviceCount(deviceList); SysUiStatsLog.write( @@ -105,6 +109,10 @@ public class MediaOutputMetricLogger { Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType); } + if (mSourceDevice == null && mTargetDevice == null) { + return; + } + updateLoggingMediaItemCount(deviceItemList); SysUiStatsLog.write( @@ -176,6 +184,10 @@ public class MediaOutputMetricLogger { Log.e(TAG, "logRequestFailed - " + reason); } + if (mSourceDevice == null && mTargetDevice == null) { + return; + } + updateLoggingDeviceCount(deviceList); SysUiStatsLog.write( @@ -201,6 +213,10 @@ public class MediaOutputMetricLogger { 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 fab8c068b2a7..78082c3eb3c6 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 @@ open class MediaTttChipControllerReceiver @Inject constructor( 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 @@ open class MediaTttChipControllerReceiver @Inject constructor( ) { animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO) } - rippleController.expandToInProgressState(rippleView, iconRippleView) } override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) { @@ -293,7 +294,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( /** 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 000000000000..c48028c31cf0 --- /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 000000000000..86f0c0d15b55 --- /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 000000000000..117203012757 --- /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 000000000000..b9f6d83d8406 --- /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 000000000000..ee1dd65b867f --- /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 000000000000..a4cd35c8a11a --- /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 000000000000..9e026576e842 --- /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 000000000000..49ac64c58cb8 --- /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 000000000000..aecec39c5c07 --- /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 000000000000..ce6ab977dea2 --- /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 000000000000..e828dbdc6c62 --- /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 ac22b7ce8b6b..779f1d8011e3 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 @@ constructor( 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 8a3ecc6afaac..0748bcbf020c 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_CANCEL; 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 class OverviewProxyService implements CallbackController<OverviewProxyLis } 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 ca8e10176e7f..02a60ad60fa1 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 @@ public class LongScreenshotActivity extends Activity { 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 e2f31e8af196..a716a6ea55fb 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.KeyEvent; 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.keyguard.dagger.KeyguardBouncerComponent; 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.shared.model.TransitionStep; 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.PhoneStatusBarViewController; 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 @@ public class NotificationShadeWindowViewController { 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 @@ public class NotificationShadeWindowViewController { mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; + private final SystemClock mClock; @Inject public NotificationShadeWindowViewController( @@ -137,8 +148,10 @@ public class NotificationShadeWindowViewController { 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 @@ public class NotificationShadeWindowViewController { 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 @@ public class NotificationShadeWindowViewController { 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 @@ public class NotificationShadeWindowViewController { 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 @@ public class NotificationShadeWindowViewController { mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( - SystemClock.uptimeMillis(), + mClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) @@ -446,10 +472,18 @@ public class NotificationShadeWindowViewController { 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 5736a5cdc05a..26149321946d 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 @@ interface SmartspaceViewComponent { 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 @@ interface SmartspaceViewComponent { 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 51c5183ffee9..cac4251bce63 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 @@ public class NotificationLockscreenUserManagerImpl implements 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 99081e98c4a3..9e2a07ea256b 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.dump.DumpManager; 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.statusbar.policy.RemoteInputView; 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.Objects; 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 @@ public class NotificationRemoteInputManager implements Dumpable { 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 @@ public class NotificationRemoteInputManager implements Dumpable { Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, StatusBarStateController statusBarStateController, RemoteInputUriController remoteInputUriController, + RemoteInputControllerLogger remoteInputControllerLogger, NotificationClickNotifier clickNotifier, ActionClickLogger logger, DumpManager dumpManager) { @@ -275,6 +279,7 @@ public class NotificationRemoteInputManager implements Dumpable { mKeyguardManager = context.getSystemService(KeyguardManager.class); mStatusBarStateController = statusBarStateController; mRemoteInputUriController = remoteInputUriController; + mRemoteInputControllerLogger = remoteInputControllerLogger; mClickNotifier = clickNotifier; dumpManager.registerDumpable(this); @@ -294,7 +299,8 @@ public class NotificationRemoteInputManager implements Dumpable { /** 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 f44f59805889..a37b2a9e530a 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 android.util.Pair; 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 @@ public class RemoteInputController { 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 @@ public class RemoteInputController { 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 class RemoteInputController { */ 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 b9ac918d50da..79d01b4a73fd 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.plugins.statusbar.StatusBarStateController.StateList 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 @@ public class StatusBarStateControllerImpl implements @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 @@ public class StatusBarStateControllerImpl implements } @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 @@ public class StatusBarStateControllerImpl implements 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 d7568a9bd89c..565c0a9426ab 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.SysuiStatusBarStateController; 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.tracing.ProtoTracer; 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 @@ public interface CentralSurfacesDependenciesModule { Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, StatusBarStateController statusBarStateController, RemoteInputUriController remoteInputUriController, + RemoteInputControllerLogger remoteInputControllerLogger, NotificationClickNotifier clickNotifier, ActionClickLogger actionClickLogger, DumpManager dumpManager) { @@ -117,6 +119,7 @@ public interface CentralSurfacesDependenciesModule { 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 000000000000..9582dfad35cd --- /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 274377f5b0dd..6f4eed3c1612 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.database.ContentObserver; 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 class NotificationInterruptStateProviderImpl implements NotificationInter public NotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, BatteryController batteryController, StatusBarStateController statusBarStateController, @@ -126,7 +121,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter UserTracker userTracker) { mContentResolver = contentResolver; mPowerManager = powerManager; - mDreamManager = dreamManager; mBatteryController = batteryController; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mStatusBarStateController = statusBarStateController; @@ -287,7 +281,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } // 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } } - - 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 a5b7e94f11e3..33cbf0699bca 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mMaxSmallHeight; private int mMaxSmallHeightLarge; private int mMaxExpandedHeight; - private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; private boolean mMustStayOnScreen; @@ -3065,14 +3064,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 25c7264af047..9df6ba9910cc 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 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro 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 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro } /** - * @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 d93c12b69ea4..5834dcbf8645 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 @@ public class NotificationContentView extends FrameLayout implements Notification 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 e2e2a2382976..c0aed7a9f983 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable }); pw.println(); pw.println("Contents:"); - 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); - } - }); + 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); + 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 0e837d2976ba..a35e5b59f765 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 @@ package com.android.keyguard; 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.Mock; import org.mockito.MockitoAnnotations; @SmallTest +@TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class KeyguardMessageAreaControllerTest extends SysuiTestCase { @Mock @@ -45,14 +49,14 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { 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 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { } @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 38d3a3eec606..f966eb33fc14 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.biometrics.SideFpsUiRequestSource; 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 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @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 1614b577a6cc..afb54d2df49f 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.LayoutInflater; 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 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { @Mock private LayoutInflater mLayoutInflater; @Mock + private AsyncLayoutInflater mAsyncLayoutInflater; + @Mock private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory; @Mock private EmergencyButtonController.Factory mEmergencyButtonControllerFactory; @@ -70,6 +74,8 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { private WindowInsetsController mWindowInsetsController; @Mock private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private FeatureFlags mFeatureFlags; private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; @@ -82,10 +88,11 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { 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 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { } @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 000000000000..070cad7302b2 --- /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 b389558faa05..d7aa6e063d1e 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 @@ package com.android.systemui.animation 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 @@ class TextAnimatorTest : SysuiTestCase() { 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 e1c74170a43f..c068efb1b5d4 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.anyString; 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 @@ public class AuthControllerTest extends SysuiTestCase { 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 5a613aa9225e..590989d3a987 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.junit.runner.RunWith 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 @@ class ControlsSettingsDialogManagerImplTest : SysuiTestCase() { } @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 3f61bf75740a..10757ae4ad99 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 @@ class ControlsUiControllerImplTest : SysuiTestCase() { 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 @@ class ControlsUiControllerImplTest : SysuiTestCase() { } @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 @@ class ControlsUiControllerImplTest : SysuiTestCase() { 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 de04ef810dd0..9df7992f979f 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 @@ class PanelTaskViewControllerTest : SysuiTestCase() { 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 b3329eb5f5b2..0e16b4771e0f 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.condition.SelfExecutingMonitor; 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 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { private Context mContext; @Mock private DreamBackend mDreamBackend; - @Mock - private SecureSettings mSecureSettings; + private FakeSettings mSecureSettings; @Mock private DreamOverlayStateController mDreamOverlayStateController; @Captor @@ -74,6 +72,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { 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 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { 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 2bcd75b7c707..18f7db18b1f4 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.Mock 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 @@ import org.mockito.Mockito.`when` as whenever */ @SmallTest class FeatureFlagsDebugTest : SysuiTestCase() { - private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug + private lateinit var featureFlagsDebug: FeatureFlagsDebug @Mock private lateinit var flagManager: FlagManager @@ -85,7 +86,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { flagMap, restarter ) - mFeatureFlagsDebug.init() + featureFlagsDebug.init() verify(flagManager).onSettingsChangedAction = any() broadcastReceiver = withArgCaptor { verify(mockContext).registerReceiver( @@ -116,7 +117,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false) assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ReleasedFlag( 2, name = "2", @@ -125,7 +126,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isTrue() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( UnreleasedFlag( 3, name = "3", @@ -134,7 +135,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isTrue() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ReleasedFlag( 4, name = "4", @@ -143,7 +144,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isFalse() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( UnreleasedFlag( 5, name = "5", @@ -157,8 +158,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { .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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false) assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ResourceBooleanFlag( 1, "1", @@ -214,16 +215,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ) ).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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ) ).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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 1, "1", @@ -286,7 +287,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isEqualTo("") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 2, "2", @@ -296,7 +297,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isEqualTo("resource2") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 3, "3", @@ -307,15 +308,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ).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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue(flag.namespace, flag.name, false) - assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse() + assertThat(featureFlagsDebug.isEnabled(flag)).isFalse() } @Test @@ -462,7 +463,33 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { .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 @@ class FeatureFlagsDebugTest : SysuiTestCase() { 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 4c6028c4c9b7..917147b17517 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.Assert.assertThrows 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 @@ import org.mockito.Mockito.`when` as whenever */ @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 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { 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 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { 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 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { 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 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { 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 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { serverFlagReader.setFlagValue(flag.namespace, flag.name, false) - assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse() + assertThat(featureFlagsRelease.isEnabled(flag)).isFalse() } @Test @@ -110,6 +118,32 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { 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 2e9800606edf..953b7fb32d56 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 android.testing.AndroidTestingRunner 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 @@ class ServerFlagReaderImplTest : SysuiTestCase() { 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 e66be08426a5..2ab1b99b7fee 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 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { } @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 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { } @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 fd353afff7c0..df13fddc5a28 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.KotlinArgumentCaptor 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 56e060d25850..17d8799b4f84 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 000000000000..ceacaf9557ca --- /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 000000000000..415e68f6013d --- /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 000000000000..0484515e38bd --- /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 000000000000..e32aac596e5b --- /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 3f940d64f236..40c733a19ccd 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 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 51492eb8e532..bdb0e7ed8d9d 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.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.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.PhoneStatusBarViewController 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.verify 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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var underTest: NotificationShadeWindowViewController + private lateinit var testScope: TestScope + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -112,6 +125,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, + featureFlags, + { + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) + }, + FakeSystemClock(), ) underTest.setupExpandedStatusBar() @@ -150,147 +183,162 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + 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(DOWN_EVENT) + + 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) - - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) - assertThat(shouldIntercept).isFalse() - } + 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(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 2f528a86cc2d..000000000000 --- 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 000000000000..5d0f408a0522 --- /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 a280510009a7..58b44ae5bcbd 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.AndroidTestingRunner 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.Mock 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 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { 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 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { 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 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @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 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { `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 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { 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 5170678cbc62..ced07348c27a 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.SysuiTestCase; 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.notification.row.ExpandableNotificationRow 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 org.mockito.MockitoAnnotations; import java.util.Optional; -import dagger.Lazy; - @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -84,6 +85,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { () -> Optional.of(mock(CentralSurfaces.class)), mStateController, mRemoteInputUriController, + mock(RemoteInputControllerLogger.class), mClickNotifier, mock(ActionClickLogger.class), mock(DumpManager.class)); @@ -141,6 +143,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, StatusBarStateController statusBarStateController, RemoteInputUriController remoteInputUriController, + RemoteInputControllerLogger remoteInputControllerLogger, NotificationClickNotifier clickNotifier, ActionClickLogger actionClickLogger, DumpManager dumpManager) { @@ -153,6 +156,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { 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 e6f272b3ad70..3327e42b930f 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 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { 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 8acf507e3452..653b0c707240 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.hardware.display.AmbientDisplayConfiguration; 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Mock PowerManager mPowerManager; @Mock - IDreamManager mDreamManager; - @Mock AmbientDisplayConfiguration mAmbientDisplayConfiguration; @Mock StatusBarStateController mStatusBarStateController; @@ -133,7 +130,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { new NotificationInterruptStateProviderImpl( mContext.getContentResolver(), mPowerManager, - mDreamManager, mAmbientDisplayConfiguration, mBatteryController, mStatusBarStateController, @@ -157,7 +153,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { // 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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 031c17fd9af0..7db219719bf0 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mNotificationInterruptStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mPowerManager, - mDreamManager, mAmbientDisplayConfiguration, mStatusBarStateController, mKeyguardStateController, @@ -730,7 +729,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { 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 class CentralSurfacesImplTest extends SysuiTestCase { 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 class CentralSurfacesImplTest extends SysuiTestCase { 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 class CentralSurfacesImplTest extends SysuiTestCase { 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { TestableNotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, StatusBarStateController controller, KeyguardStateController keyguardStateController, @@ -1415,7 +1413,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { 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 e185922d47d6..8e3988b2c038 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 @@ public class BubblesTest extends SysuiTestCase { private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private UserHandle mUser0; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -301,6 +303,8 @@ public class BubblesTest extends SysuiTestCase { // 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 @@ public class BubblesTest extends SysuiTestCase { 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 @@ public class BubblesTest extends SysuiTestCase { 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 @@ public class BubblesTest extends SysuiTestCase { @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 @@ public class BubblesTest extends SysuiTestCase { @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, 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); + 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 @@ public class BubblesTest extends SysuiTestCase { 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 ceee0bc466d2..4e14bbf6ac1f 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.content.ContentResolver; 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 @@ public class TestableNotificationInterruptStateProviderImpl TestableNotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, StatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, @@ -53,7 +51,6 @@ public class TestableNotificationInterruptStateProviderImpl 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 607439b79d91..bd6788993301 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.graphics.PointF; 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 @@ class InputController { 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 e9a7f205c519..d94f4f22f2c9 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -194,19 +194,19 @@ public final class BatteryService extends SystemService { 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 19e5cb142cfd..a3dc21e70281 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -320,7 +320,7 @@ public final class DropBoxManagerService extends SystemService { .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 c4418592226d..409f0541eed7 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -35,7 +35,7 @@ per-file MmsServiceBroker.java = file:/telephony/OWNERS 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 d1e0f16bd0ae..3de65f94decf 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -79,6 +79,7 @@ public class RescueParty { 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 @@ public class RescueParty { @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 @@ public class RescueParty { } } - 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 @@ public class RescueParty { * 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 @@ public class RescueParty { } 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 @@ public class RescueParty { 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 @@ public class RescueParty { 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 @@ public class RescueParty { 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 @@ public class RescueParty { if (isDisabled()) { return false; } + boolean mayPerformReboot = !shouldThrottleReboot(); executeRescueLevel(mContext, /*failedPackage=*/ null, - getRescueLevel(mitigationCount, true)); + getRescueLevel(mitigationCount, mayPerformReboot)); return true; } @@ -698,14 +703,27 @@ public class RescueParty { * 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 c5008fa2e07e..4a0a228ce645 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 569600482873..4fa28a11c0a4 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { // 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 2a1e860740a6..755019684da6 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - // 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 53fcddf215c6..33d4004a9027 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 @@ public class BroadcastConstants { 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 @@ public class BroadcastConstants { 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 @@ public class BroadcastConstants { 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 59f33ddb795d..6bd3c7953e01 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 class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { pushMessageOverQuota = from.pushMessageOverQuota; interactive = from.interactive; shareIdentity = from.shareIdentity; + urgent = from.urgent; filterExtrasForReceiver = from.filterExtrasForReceiver; } @@ -681,15 +692,8 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { } } + /** + * 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 ddc9e9166faa..844f175b9b25 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.net.Uri; 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 @@ final class CoreSettingsObserver extends ContentObserver { 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 bac9253d5b93..8e93c1b8e2ac 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 @@ public final class ProcessStatsService extends IProcessStats.Stub { // 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 @@ public final class ProcessStatsService extends IProcessStats.Stub { 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 @@ public final class ProcessStatsService extends IProcessStats.Stub { @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 490a33eee4e6..3e86c454fae8 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub //----------------------------------------------------------------- // 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 5cdf58bdd62f..394e4af30a9e 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 @@ class RotationHelper { 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 @@ class RotationHelper { * - 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 @@ class RotationHelper { 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 @@ class RotationHelper { 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 @@ class RotationHelper { 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 3dac04cc1426..c2483671a065 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.provider.Settings; 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 com.android.server.utils.EventLogger; 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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { // 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 @@ public class SoundDoseHelper { * 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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { } /*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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { } /*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 @@ public class SoundDoseHelper { } /*package*/ boolean safeDevicesContains(int device) { - return mSafeMediaVolumeDevices.containsKey(device); + return mSafeMediaVolumeDevices.indexOfKey(device) >= 0; } /*package*/ void invalidatPendingVolumeCommand() { @@ -665,9 +662,9 @@ public class SoundDoseHelper { 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 @@ public class SoundDoseHelper { } 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 3ea4f4f8dc18..8f54e45a1533 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 @@ public class SpatializerHelper { if (transform.length != 6) { throw new IllegalArgumentException("invalid array size" + transform.length); } - if (!checkSpatForHeadTracking("setGlobalTransform")) { + if (!checkSpatializerForHeadTracking("setGlobalTransform")) { return; } try { @@ -1077,7 +1077,7 @@ public class SpatializerHelper { } synchronized void recenterHeadTracker() { - if (!checkSpatForHeadTracking("recenterHeadTracker")) { + if (!checkSpatializerForHeadTracking("recenterHeadTracker")) { return; } try { @@ -1087,8 +1087,30 @@ public class SpatializerHelper { } } + 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 @@ public class SpatializerHelper { 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 @@ public class SpatializerHelper { 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 4e3de3cf9506..e8af840afe6e 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 class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService mLatencyMs = latencyMs; mOperatingMode = operatingMode; mDeviceError = deviceError; + mLogId = logId; } public void markCompleted(int internalReconfigure, long requestCount, @@ -840,7 +843,8 @@ public class CameraServiceProxy extends SystemService + ", 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 a589313a1e92..00af22468fd3 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 @@ public final class DeviceState { * 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 @@ public final class DeviceState { 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 43ee5e268fe0..964569008acc 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 android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA 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 @@ public final class DeviceStateManagerService extends SystemService { @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 @@ public final class DeviceStateManagerService extends SystemService { 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 900874044881..ab261ac24091 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { } 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { */ 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { .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 @@ class DeviceStateNotificationController extends BroadcastReceiver { 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { new NotificationInfo( names[i], activeNotificationTitles[i], activeNotificationContents[i], thermalCriticalNotificationTitles[i], - thermalCriticalNotificationContents[i]) + thermalCriticalNotificationContents[i], + powerSaveModeNotificationTitles[i], + powerSaveModeNotificationContents[i]) ); } @@ -272,16 +314,21 @@ class DeviceStateNotificationController extends BroadcastReceiver { 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 @@ class DeviceStateNotificationController extends BroadcastReceiver { 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 fecc13fd0d03..af33de0426b1 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 @@ public interface DeviceStateProvider { */ 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 2ed4765874f7..46f0bc0d9805 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 @@ final class OverrideRequestController { */ 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 @@ final class OverrideRequestController { @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 @@ final class OverrideRequestController { 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 @@ final class OverrideRequestController { 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 @@ final class OverrideRequestController { 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 @@ final class OverrideRequestController { * 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 db6944d011c9..3864200a8cb0 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 @@ public class DisplayModeDirector { } @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 3e2efdd7db85..20ff51c22783 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 @@ final class DreamController { 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 4d03e44bfd19..7e990c6c2f4a 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 class InputManagerInternal { */ 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 b2b22a0a083a..efc4f11168bb 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 @@ public class InputManagerService extends IInputManager.Stub 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 @@ public class InputManagerService extends IInputManager.Stub @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 f873a1b867d2..4d4a87e18664 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.inputmethod.InputMethodSubtypeHandle; 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 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { 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 22226e88f15f..5395302d1c32 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 @@ interface NativeInputManagerService { /** 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 @@ interface NativeInputManagerService { @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 9f7ff3119dde..0ae1e8076b81 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 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { 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 91f91f86d275..b336b95d793f 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** {@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 3329f549d381..030c96e3dba6 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 @@ public class ConditionProviders extends ManagedServices { } } - 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 @@ public class ConditionProviders extends ManagedServices { 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 c2e8df1dd70a..46337a909539 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 @@ public class NotificationManagerService extends SystemService { * 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 5e0a18039152..8417049bf297 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 @@ import java.util.concurrent.TimeUnit; 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { // 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { mEnabled = true; } + @Override public RankingReconsideration process(NotificationRecord record) { if (!mEnabled) { if (VERBOSE) Slog.i(TAG, "disabled"); @@ -272,7 +274,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } - 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } + 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 c232b3698bc0..9748aba84c73 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.SharedLibraryInfo; 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 @@ public interface Computer extends PackageDataSnapshot { 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 @@ public interface Computer extends PackageDataSnapshot { * 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 5984360a534c..acd4a96c2817 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 @@ public class ComputerEngine implements Computer { @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 @@ public class ComputerEngine implements Computer { @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 @@ public class ComputerEngine implements Computer { @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 d39cac070413..c29e4d78f929 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 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @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 703bbda92182..420e2e961d7e 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 @@ final class IncrementalProgressListener extends IPackageLoadingProgressCallback. 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 fa535c38c5d2..03e0d360f9e3 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 d3ee52c48448..6bc876037cfb 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 2a1172c93d74..839ff415252c 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.SigningInfo; 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal private String mPathString; private float mLoadingProgress; + private long mLoadingCompletedTime; @Nullable private String mPrimaryCpuAbi; @@ -630,6 +632,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } + public PackageSetting setLoadingCompletedTime(long loadingCompletedTime) { + mLoadingCompletedTime = loadingCompletedTime; + onChanged(); + return this; + } + @NonNull @Override public long getVersionCode() { @@ -1489,6 +1501,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated.Member + public long getLoadingCompletedTime() { + return mLoadingCompletedTime; + } + + @DataClass.Generated.Member public @Nullable String getCpuAbiOverride() { return mCpuAbiOverride; } @@ -1563,10 +1580,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @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 b6557d000463..94a00d6e2e48 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile 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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .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 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } 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 eb37302817c5..721ad889f7fe 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 class UserManagerInternal { 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 @@ public abstract class UserManagerInternal { * * <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 cde8bd7447e6..fdc2affda8e7 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 @@ public class UserManagerService extends IUserManager.Stub { // 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 @@ public class UserManagerService extends IUserManager.Stub { } /** - * @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 @@ public class UserManagerService extends IUserManager.Stub { } @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 12c9e9804a60..2f99062df28e 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 @@ class UserSystemPackageInstaller { 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 a8615c2c6200..3710af6771b4 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 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * 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 @@ public final class UserVisibilityMediator implements Dumpable { } } + /** 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 5b967ec20cd3..f340f9374dd5 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.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; 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 class VerifyingSession { 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 2f4c02774e4d..3a0ff2736c6d 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 @@ public interface PackageStateInternal extends PackageState { 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 5947d4735faa..8125b0f662aa 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 @@ public class PackageStateMutator { @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 c610c02a6e9c..55d96f3aee08 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 @@ public interface PackageStateWrite { 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 8d7f78209a4e..3644054e3b78 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 static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA 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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, @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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, } } + @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 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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 da7aaa4fd478..d0ed9bfbb285 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 @@ public class Notifier { 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 8e8abf641a5a..96f4a01f7f3a 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn 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 bc73db18b379..bff6d502d566 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 @@ final class SpeechRecognitionManagerServiceImpl extends @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 601d0e20d489..3d8f538cc7ad 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_PAID; 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.bluetooth.UidTraffic; 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 @@ public class StatsPullAtomService extends SystemService { 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 ed9177546794..2d3928ca5721 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (currentLowestPriority > priority) { inUseLowestPriorityFrHandle = fr.getHandle(); currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(fr.getOwnerClientId())).getProcessId()); } } } @@ -1063,7 +1066,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde // 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (currentLowestPriority > priority) { inUseLowestPriorityLnbHandle = lnb.getHandle(); currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(lnb.getOwnerClientId())).getProcessId()); } } } @@ -1208,7 +1215,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde // 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde // update lowest priority if (currentLowestPriority > priority) { currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(dr.getOwnerClientId())).getProcessId()); shouldUpdate = true; } // update smallest caps @@ -1473,7 +1493,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde // 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 d108f0de5d15..f14a432f73ae 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 @@ class ActivityClientController extends IActivityClientController.Stub { // {@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 d15d09440573..324a0adee693 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - 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 5d038dcab73a..be7d9b63f779 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 @@ class ActivityRecordInputSink { } 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 211c230b4ece..ce29564d0b02 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { // 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 f9f972c20ac6..b67bc62e52f1 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 @@ class BackNavigationController { 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 dde89e9bca2e..9cc311dc6c8e 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 @@ class EnsureActivitiesVisibleHelper { } 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 @@ class EnsureActivitiesVisibleHelper { } 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 1e9d451b1a69..0a47fe09dbd9 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 static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSI 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } @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 f355f088b608..5db39fc8434c 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 @@ final class LetterboxUiController { @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 @@ final class LetterboxUiController { // 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 969f65c9602b..67ca8443102b 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 @@ class Task extends TaskFragment { 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 2ddb307ea430..7c57dc17e802 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 4e0f120759d9..370d304ed324 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { dc.removeImeSurfaceImmediately(); dc.handleCompleteDeferredRemoval(); } + validateVisibility(); mState = STATE_FINISHED; mController.mTransitionTracer.logState(this); @@ -995,6 +1028,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Handle back animation if it's already started. mController.mAtm.mBackNavigationController.handleDeferredBackAnimation(mTargets); + mController.mFinishingTransition = null; } void abort() { @@ -1173,14 +1207,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mFinishTransaction != null) { mFinishTransaction.apply(); } - mController.finishTransition(mToken); + mController.finishTransition(this); } private void cleanUpInternal() { @@ -1888,6 +1921,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 bacc6e615ed1..86bb6b58d14c 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 @@ class TransitionController { */ 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 @@ class TransitionController { 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 @@ class TransitionController { } 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 @@ class TransitionController { } /** @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 2f3a70eb0e2d..969afe544b18 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 @@ public abstract class WindowManagerInternal { /** * 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 @@ public abstract class WindowManagerInternal { /** * 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 45cdacd503a8..42d23e755b21 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.database.ContentObserver; 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 @@ public class WindowManagerService extends IWindowManager.Stub .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 c3c87af51b15..17d4f1be011e 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub */ 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // 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 694f1be67d1a..834b708f3f9a 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 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } /** + * 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 2767972f7ea0..424b0436a008 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 @@ package com.android.server.wm; 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 @@ final class WindowProcessControllerMap { 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 f86b997b6c8e..d6c03113e87f 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.IWindow; 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private InsetsState mFrozenInsetsState; - private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; private KeyInterceptionInfo mKeyInterceptionInfo; /** @@ -1504,13 +1495,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP || (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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP .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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - /** - * 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 b4e2fb6ca3e3..da44da4f2838 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 @@ public: void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); + FloatPoint getMouseCursorPosition(); /* --- InputReaderPolicyInterface implementation --- */ @@ -366,7 +367,7 @@ public: 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 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerCon 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 @@ bool NativeInputManager::isPerDisplayTouchModeEnabled() { 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 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native 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 @@ static const JNINativeMethod gInputManagerMethods[] = { {"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 793d83e90cfe..4f8235a11b2a 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 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR 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 4c5c366b6a82..85a48d9838d1 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 @@ public final class CredentialManagerService } 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 ed139b57da3d..99f3b3efe838 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 @@ public class MetricUtilities { } /** + * 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 @@ public class MetricUtilities { * @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 @@ public class MetricUtilities { * 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 950cf4f9a482..b86dabaa8503 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 @@ import android.util.Slog; * * @hide */ -public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest, +public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest, Void> implements RemoteCredentialService.ProviderCallbacks<Void> { @@ -42,7 +42,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential 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 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential clearRequestSession.mClientRequest, clearRequestSession.mClientAppInfo); return new ProviderClearSession(context, providerInfo, clearRequestSession, userId, - remoteCredentialService, providerRequest); + remoteCredentialService, providerRequest); } @Nullable @@ -90,6 +91,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential if (exception instanceof ClearCredentialStateException) { mProviderException = (ClearCredentialStateException) exception; } + captureCandidateFailure(); updateStatusAndInvokeCallback(toStatus(errorCode)); } @@ -120,14 +122,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential @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 3ec0fc0d4646..bbbb15666028 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.Log; 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 @@ public final class ProviderCreateSession extends ProviderSession< 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 @@ public final class ProviderCreateSession extends ProviderSession< // Store query phase exception for aggregation with final response mProviderException = (CreateCredentialException) exception; } + captureCandidateFailure(); updateStatusAndInvokeCallback(toStatus(errorCode)); } @@ -175,14 +179,32 @@ public final class ProviderCreateSession extends ProviderSession< 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 @@ public final class ProviderCreateSession extends ProviderSession< @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 @@ public final class ProviderCreateSession extends ProviderSession< } 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 @@ public final class ProviderCreateSession extends ProviderSession< setRemoteEntry(remoteEntry); } } + public void addCreateEntry(CreateEntry createEntry) { String id = generateUniqueId(); Entry entry = new Entry(SAVE_ENTRY_KEY, @@ -373,6 +391,7 @@ public final class ProviderCreateSession extends ProviderSession< 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 ec8bf22e4514..bf1db373446b 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.Log; 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential Log.i(TAG, "Unable to create provider session"); return null; } + private static BeginGetCredentialRequest constructQueryPhaseRequest( android.credentials.GetCredentialRequest filteredRequest, CallingAppInfo callingAppInfo, @@ -169,7 +173,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential if (exception instanceof GetCredentialException) { mProviderException = (GetCredentialException) exception; } + captureCandidateFailure(); updateStatusAndInvokeCallback(toStatus(errorCode)); } @@ -269,20 +274,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential .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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential setRemoteEntry(remoteEntry); } } + public void addCredentialEntry(CredentialEntry credentialEntry) { String id = generateUniqueId(); Entry entry = new Entry(CREDENTIAL_ENTRY_KEY, @@ -559,7 +585,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential } - 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 77d4e7750038..faa91dce7b92 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.os.RemoteException; 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 @@ public abstract class ProviderSession<T, R> @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 @@ public abstract class ProviderSession<T, R> mUserId = userId; mComponentName = componentName; mRemoteCredentialService = remoteCredentialService; - mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(); mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); } @@ -208,6 +209,12 @@ public abstract class ProviderSession<T, R> 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 @@ public abstract class ProviderSession<T, R> } 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 ed42bb2f010a..3ac10c9a7d22 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 @@ package com.android.server.credentials; 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 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan 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 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan 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 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan 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 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); if (isSessionCancelled()) { + MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter); finishSession(/*propagateCancellation=*/true); return; } @@ -249,13 +261,8 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan } 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 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan * @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 c392d7806910..f00c7f46c5ae 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 android.util.Log; 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 @@ public class CandidatePhaseMetric { 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 class CandidatePhaseMetric { 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 6cd9f1c3f9a0..a1789b2b125d 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 be2873e9af96..b93350893a81 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1793,6 +1793,10 @@ public final class SystemServer implements Dumpable { } 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 @@ public final class SystemServer implements Dumpable { 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 7e440494a1e4..7d4f87d73507 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.mock; 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 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe super.setUp(); mVisibilityApplier = (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); - mInputMethodManagerService.mCurFocusedWindowClient = mock( - InputMethodManagerService.ClientState.class); + mInputMethodManagerService.setAttachedClientForTesting( + mock(InputMethodManagerService.ClientState.class)); } @Test @@ -119,4 +125,38 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe 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 289439561178..24e380ccd82e 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 { } 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 { } 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 index 25743b87cabd..383e60ab3b41 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 index 9bf925417e49..fe7a212a1d03 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 e711cabe75b2..1146271368af 100644 --- a/services/tests/PackageManagerServiceTests/server/Android.bp +++ b/services/tests/PackageManagerServiceTests/server/Android.bp @@ -127,10 +127,10 @@ java_genrule { ":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 ebf309f4b796..906cc83aea33 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 @@ public class PackageParserLegacyCoreTest { 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 5377ee71f480..3a47b476a131 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.assertFalse; 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 83441bfb8d1e..1a7517098d18 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.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; /** * Test RescueParty. @@ -94,6 +95,9 @@ public class RescuePartyTest { "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 @@ public class RescuePartyTest { } @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 b395f42478b1..174141101dfc 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_STANDALONE; 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.IAlarmListener; 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.Context; 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.quality.Strictness; 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 @@ public final class AlarmManagerServiceTest { @Mock private PackageManagerInternal mPackageManagerInternal; @Mock - private RoleManager mRoleManager; - @Mock private AppStateTrackerImpl mAppStateTracker; @Mock private AlarmManagerService.ClockReceiver mClockReceiver; @@ -393,11 +387,6 @@ public final class AlarmManagerServiceTest { } @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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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,57 +2172,69 @@ public final class AlarmManagerServiceTest { } } + private void mockChangeEnabled(long changeId, boolean enabled) { + doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), + any(UserHandle.class))); + doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyInt())); + } + + @Test + 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 hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException { + public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmState(true, false, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, false, MODE_IGNORED); + mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test - public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException { + public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmState(true, true, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, true, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, true, MODE_IGNORED); + mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(true, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(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))); - doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyInt())); - } - @Test - public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException { + public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmState(false, false, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(false, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmState(false, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @@ -2239,61 +2243,94 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { @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 final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { @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 final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { .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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { } @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 @@ public final class AlarmManagerServiceTest { } @Test - public void hasUseExactAlarmPermission() { + public void hasUseExactAlarmInternal() { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockUseExactAlarmState(true); @@ -3520,7 +3526,7 @@ public final class AlarmManagerServiceTest { } @Test - public void hasUseExactAlarmPermissionChangeDisabled() { + public void hasUseExactAlarmInternalChangeDisabled() { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, false); mockUseExactAlarmState(true); @@ -3531,6 +3537,49 @@ public final class AlarmManagerServiceTest { } @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 9263bffc48eb..d56229c9681f 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 @@ public class ApplicationExitInfoTest { 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 01e27684aaf7..2b6f2174d49b 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_BROA 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 static org.mockito.Mockito.doReturn; 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 androidx.test.filters.SmallTest; 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 @@ public class BroadcastRecordTest { 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 @@ public class BroadcastRecordTest { } @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 000000000000..fd1b06830a89 --- /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 8b420a36602c..e056417811c7 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.UiModeManager; 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 @@ import java.time.ZoneOffset; 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { .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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 239b6fd4ed00..70b5ac063316 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 @@ abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase { .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 @@ abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase { .that(mMediator.unassignUserFromExtraDisplay(userId, displayId)) .isTrue(); expectUserIsNotVisibleOnDisplay(userId, displayId); + expectDisplaysAssignedToUserDoesNotContainDisplayId(userId, displayId); } } @@ -668,6 +670,7 @@ abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase { 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 @@ abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase { .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 71280ce01779..e81b63c8c9b1 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 @@ public class ScribeTest { 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 7642e7bc3b91..6c6b60803ed3 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 static org.mockito.Mockito.verify; 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 @@ public class InputControllerTest { 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 @@ public class InputControllerTest { 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 8196d6a35cbd..e396263b1679 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 @@ public class DeviceStateNotificationControllerTest { 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 @@ public class DeviceStateNotificationControllerTest { 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 @@ public class DeviceStateNotificationControllerTest { 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 @@ public class DeviceStateNotificationControllerTest { } @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 @@ public class DeviceStateNotificationControllerTest { // 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 @@ public class DeviceStateNotificationControllerTest { 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 7125796e7b98..7e40f96154d2 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 @@ package com.android.server.policy; 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 @@ public final class DeviceStateProviderImplTest { + " <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 @@ public final class DeviceStateProviderImplTest { 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 @@ public final class DeviceStateProviderImplTest { 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 @@ public final class DeviceStateProviderImplTest { 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 6edef75b645d..07b434538c7b 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.content.ContextWrapper; 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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { 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 000000000000..b94ed012c385 --- /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 41a9504fba97..5cbd1204cf49 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 d72cfc70fc02..0564a73bf3fc 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 android.util.LruCache; 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 org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) @@ -215,7 +220,7 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { // 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { // 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { // 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { 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 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { 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 06b6ed83d28c..da078a22c5ff 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 @@ public class LetterboxUiControllerTest extends WindowTestsBase { 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 @@ public class LetterboxUiControllerTest extends WindowTestsBase { 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 753cc623cf25..2cc46a9c587d 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_CHANG 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 @@ public class SizeCompatTests extends WindowTestsBase { 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 304581297932..616d528c67fa 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_SHOW_WALLPAPER; 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.Assert.assertTrue; 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 65631ea11e81..984b868ab67f 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 @@ public class WallpaperControllerTests extends WindowTestsBase { 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 a68a573079a3..17ad4e3a6d68 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { // 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 c2ee0798fd07..2a3c9bca0cc6 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.assertEquals; 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 @@ public class WindowProcessControllerMapTests extends WindowTestsBase { 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 d6cfd000abd0..cf839812f6aa 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 @@ public class WindowProcessControllerTests extends WindowTestsBase { } @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 ce6cd9023e5c..b80500ad6417 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 @@ class WindowTestsBase extends SystemServiceTestsBase { } 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 bf12b9cce302..212dc41b3de3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1628,6 +1628,7 @@ public class CarrierConfigManager { * <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 @@ public class CarrierConfigManager { /** * 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 @@ public class CarrierConfigManager { 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 dd080856d450..1668193e076c 100644 --- a/telephony/java/android/telephony/data/QosBearerSession.java +++ b/telephony/java/android/telephony/data/QosBearerSession.java @@ -102,12 +102,11 @@ public final class QosBearerSession implements Parcelable{ 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 98221c9ef2c5..cd9d81e1ee9b 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl @@ -22,13 +22,6 @@ package android.telephony.satellite; */ 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 index d3f1091acfa0..24420833bdb6 100644 --- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl @@ -22,7 +22,7 @@ import android.telephony.satellite.PointingInfo; * 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 a3c3f1966c94..7c79447399b2 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -30,31 +30,12 @@ public final class PointingInfo implements Parcelable { /** 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 final class PointingInfo implements Parcelable { 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 @@ public final class PointingInfo implements Parcelable { 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 @@ public final class PointingInfo implements Parcelable { 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 889856b09ae6..df80159780ec 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -33,31 +33,24 @@ public final class SatelliteCapabilities implements Parcelable { @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 @@ public final class SatelliteCapabilities implements Parcelable { 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 @@ public final class SatelliteCapabilities implements Parcelable { sb.append("none,"); } - sb.append("isAlwaysOn:"); - sb.append(mIsAlwaysOn); - sb.append(","); - - sb.append("needsPointingToSatellite:"); - sb.append(mNeedsPointingToSatellite); + sb.append("isPointingRequired:"); + sb.append(mIsPointingRequired); sb.append(","); - sb.append("needsSeparateSimProfile:"); - sb.append(mNeedsSeparateSimProfile); + sb.append("maxBytesPerOutgoingDatagram"); + sb.append(mMaxBytesPerOutgoingDatagram); return sb.toString(); } @@ -133,33 +121,22 @@ public final class SatelliteCapabilities implements Parcelable { } /** - * 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 @@ public final class SatelliteCapabilities implements Parcelable { } } - 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 cc5a9f45de83..d3cb8a07e4ba 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 @@ public final class SatelliteDatagram implements Parcelable { /** * Datagram to be sent or received over satellite. */ - private byte[] mData; + @NonNull private byte[] mData; /** * @hide @@ -51,8 +50,8 @@ public final class SatelliteDatagram implements Parcelable { 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 @@ public final class SatelliteDatagram implements Parcelable { } }; - @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 213b98549344..f237ada9d1e0 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 @@ public class SatelliteDatagramCallback { * 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 9ec6929543de..d0abfbf80e76 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -36,12 +36,15 @@ import android.telephony.SubscriptionManager; 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 @@ import java.util.function.Consumer; 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 @@ public class SatelliteManager { * 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 @@ public class SatelliteManager { /** * 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 @@ public class SatelliteManager { /** * 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 class SatelliteManager { 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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { } /** - * 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 @@ public class SatelliteManager { * @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 @@ public class SatelliteManager { } } }; - 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 class SatelliteManager { 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); - - 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); + Objects.requireNonNull(resultListener); + ISatelliteTransmissionUpdateCallback internalCallback = + sSatelliteTransmissionUpdateCallbackMap.remove(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 @@ public class SatelliteManager { * * @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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { * {@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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { 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 class SatelliteManager { 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 @@ public class SatelliteManager { 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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { /** * 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 @@ public class SatelliteManager { * @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 @@ public class SatelliteManager { 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 @@ public class SatelliteManager { * 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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { * 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 @@ public class SatelliteManager { } /** - * 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 @@ public class SatelliteManager { */ @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 @@ public class SatelliteManager { 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 d44a84d6db88..000000000000 --- 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 2b6a5d97d3a6..a62eb8b8a5fb 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 17d05b79fef5..d9ecaa3467e3 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,80 +16,15 @@ package android.telephony.satellite; -import android.annotation.NonNull; -import android.os.Binder; - -import java.util.concurrent.Executor; - /** * A callback class for monitoring satellite modem state change events. * * @hide */ -public class SatelliteStateCallback { - private final CallbackBinder mBinder = new CallbackBinder(this); - - private static class CallbackBinder extends ISatelliteStateCallback.Stub { - private final SatelliteStateCallback mLocalCallback; - private Executor mExecutor; - - private CallbackBinder(SatelliteStateCallback localCallback) { - mLocalCallback = localCallback; - } - - @Override - public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> - mLocalCallback.onSatelliteModemStateChanged(state)); - } finally { - restoreCallingIdentity(callingIdentity); - } - } - - @Override - public void onPendingDatagramCount(int count) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> - mLocalCallback.onPendingDatagramCount(count)); - } finally { - restoreCallingIdentity(callingIdentity); - } - } - - private void setExecutor(Executor executor) { - mExecutor = executor; - } - } - +public interface SatelliteStateCallback { /** * Called when satellite modem state changes. * @param state The new satellite modem state. */ - public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { - // Base Implementation - } - - /** - * Called when there are pending datagrams to be received from satellite. - * @param count Pending datagram count. - */ - public void onPendingDatagramCount(int count) { - // Base Implementation - } - - //TODO: Add an API for datagram transfer state update here. - - /**@hide*/ - @NonNull - final ISatelliteStateCallback getBinder() { - return mBinder; - } - - /**@hide*/ - public void setExecutor(@NonNull Executor executor) { - mBinder.setExecutor(executor); - } + void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java new file mode 100644 index 000000000000..0efbd1fbdfef --- /dev/null +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -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 android.telephony.satellite; + +import android.annotation.NonNull; + +/** + * A callback class for monitoring satellite position update and datagram transfer state change + * events. + * + * @hide + */ +public interface SatelliteTransmissionUpdateCallback { + /** + * Called when the satellite position changed. + * + * @param pointingInfo The pointing info containing the satellite location. + */ + void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); + + /** + * Called when satellite datagram 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. + */ + 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 d93ee217c2fb..a780cb936ebd 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -49,10 +49,9 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - void requestSatelliteCapabilities(in IIntegerConsumer errorCallback, + void requestSatelliteCapabilities(in IIntegerConsumer resultCallback, in ISatelliteCapabilitiesConsumer callback); /** @@ -154,7 +155,7 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * * @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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - void requestIsSatelliteProvisioned(in IIntegerConsumer errorCallback, + void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); /** @@ -280,7 +262,7 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 @@ oneway interface ISatellite { * 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 d9668687e6e6..5e692151c604 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -43,10 +43,8 @@ oneway interface ISatelliteListener { /** * 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 @@ oneway interface ISatelliteListener { * @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 83392dd3585e..52a36d8b29a3 100644 --- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl +++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl @@ -29,21 +29,4 @@ parcelable PointingInfo { * 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 10c2ea384e1b..cd69da18c5b0 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl @@ -28,18 +28,12 @@ parcelable SatelliteCapabilities { 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 711dcbe3f62b..debb394ed234 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -70,20 +70,21 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { * 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 @@ public class SatelliteImplBase extends SatelliteService { * 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 @@ public class SatelliteImplBase extends SatelliteService { * 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 @@ public class SatelliteImplBase extends SatelliteService { } /** - * 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 @@ public class SatelliteImplBase extends SatelliteService { * 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 @@ public class SatelliteImplBase extends SatelliteService { * 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 @@ public class SatelliteImplBase extends SatelliteService { * 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 323fa6faaf09..d0de3acdf257 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.IImsRegistration; 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 @@ interface ITelephony { * * @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 @@ interface ITelephony { 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 @@ interface ITelephony { */ @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 @@ interface ITelephony { 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. - */ - @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. + * @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 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 @@ interface ITelephony { * @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 @@ interface ITelephony { * * @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 @@ interface ITelephony { * 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 @@ interface ITelephony { * 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 @@ interface ITelephony { * @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 d2a6bf288be4..81efda17abe3 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 @@ public final class BackgroundDexOptServiceIntegrationTests { @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 000000000000..719a89817a9d --- /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 000000000000..a1bf42a44e86 --- /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 000000000000..1eb773dc19b8 --- /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 000000000000..18e3aecfa832 --- /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 000000000000..a4ac1d7c6134 --- /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/tests/EnforcePermission/service-app/AndroidManifest.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml new file mode 100644 index 000000000000..ddafe15ab88f --- /dev/null +++ b/tests/EnforcePermission/service-app/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?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.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 000000000000..7879a1214c01 --- /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 000000000000..e9b897db1294 --- /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 000000000000..cd53854189b7 --- /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 000000000000..4a0c6a86628f --- /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 000000000000..120381a7fb83 --- /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 000000000000..d2a4a037f125 --- /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 000000000000..3289bc601160 --- /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 8fdbb6445bac..d0dc42f29b9e 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 @@ open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition /** {@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 000000000000..f75d9eede25b --- /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 000000000000..4aa78d4482fb --- /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 000000000000..9679059e5069 --- /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 e704cc29976e..46ebfede9a42 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 org.junit.runner.RunWith; 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 @@ public class LocaleStoreTest { 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 f183a9b1d46c..b7f5b15f72ac 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.content.Intent; 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 @@ public class MainInteractionService extends VoiceInteractionService { 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 5fdfb66bdf4e..2ce2167c1bf3 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -209,6 +209,8 @@ class LinkCommand : public Command { 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 c66f4e5b7c30..a43bf1b60f42 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -2120,6 +2120,33 @@ class InputType : public ManifestExtractor::Element { } }; +/** 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 @@ template <typename T> 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 @@ constexpr const char* GetExpectedTagForType() { 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 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"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 56d90758ee73..53f0abee1cf2 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -749,6 +749,23 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { 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 90f5177e752e..175ab6f1cd7b 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 @@ struct ManifestFixerOptions { // '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 7180ae6b8bc7..1b8f05b957a7 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -965,6 +965,63 @@ TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) { 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 4809befb8d20..63e471b25ffb 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 @@ import java.util.Objects; public final class SharedConnectivitySettingsState implements Parcelable { private final boolean mInstantTetherEnabled; + private final PendingIntent mInstantTetherSettingsPendingIntent; private final Bundle mExtras; /** @@ -44,9 +49,13 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ 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 @@ public final class SharedConnectivitySettingsState implements Parcelable { } /** + * 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 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ @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 @@ public final class SharedConnectivitySettingsState implements Parcelable { } /** + * 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 final class SharedConnectivitySettingsState implements Parcelable { 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 @@ public final class SharedConnectivitySettingsState implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0); dest.writeBoolean(mInstantTetherEnabled); dest.writeBundle(mExtras); } @@ -135,8 +181,10 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ @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 final class SharedConnectivitySettingsState implements Parcelable { 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 57108e4aa227..87ca99fd3e03 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 @@ public abstract class SharedConnectivityService extends Service { 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 @@ public abstract class SharedConnectivityService extends Service { @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 c9105f7454e4..7a299694741a 100644 --- a/wifi/tests/Android.bp +++ b/wifi/tests/Android.bp @@ -36,6 +36,7 @@ android_test { 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 7578dfd11225..71239087b2bd 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 @@ public class SharedConnectivityManagerTest { @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 752b74905c97..5e17dfba9790 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 @@ package android.net.wifi.sharedconnectivity.app; 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 @@ import org.junit.Test; @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 class SharedConnectivitySettingsStateTest { 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 @@ public class SharedConnectivitySettingsStateTest { 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 @@ public class SharedConnectivitySettingsStateTest { } 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 b8b6b767eed3..514ba3c0472e 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 @@ public class SharedConnectivityServiceTest { .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 @@ public class SharedConnectivityServiceTest { 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 @@ public class SharedConnectivityServiceTest { service.attachBaseContext(mContext); return service; } + + private SharedConnectivitySettingsState buildSettingsState() { + return new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true) + .setInstantTetherSettingsPendingIntent(new Intent()) + .setExtras(Bundle.EMPTY).build(); + } } |