summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java119
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java6
-rw-r--r--core/api/test-current.txt7
-rw-r--r--core/java/android/content/Intent.java2
-rw-r--r--core/java/android/content/pm/UserProperties.java67
-rw-r--r--core/java/android/security/FileIntegrityManager.java8
-rw-r--r--core/java/android/view/HdrRenderState.java121
-rw-r--r--core/java/android/view/ViewRootImpl.java53
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java37
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl3
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig12
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java50
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java38
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java69
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt8
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig14
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt115
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt200
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt481
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt125
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt2
-rw-r--r--packages/SystemUI/res/layout/screen_record_options.xml45
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt128
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/model/UiModeNightTileModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileModelHelper.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java22
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java20
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java1
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java1
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig8
-rw-r--r--services/core/java/com/android/server/audio/AdiDeviceState.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java8
-rw-r--r--services/core/java/com/android/server/content/SyncJobService.java16
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java139
-rw-r--r--services/core/java/com/android/server/utils/Android.bp10
-rw-r--r--services/core/java/com/android/server/utils/AnrTimer.java (renamed from services/core/java/com/android/server/am/AnrTimer.java)20
-rw-r--r--services/core/java/com/android/server/utils/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java17
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java291
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java48
-rw-r--r--services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java389
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java132
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java203
-rw-r--r--tests/FlickerTests/ActivityEmbedding/Android.bp2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/Android.bp12
-rw-r--r--tests/FlickerTests/AppClose/Android.bp1
-rw-r--r--tests/FlickerTests/AppClose/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/AppLaunch/Android.bp2
-rw-r--r--tests/FlickerTests/AppLaunch/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/FlickerService/Android.bp1
-rw-r--r--tests/FlickerTests/FlickerService/trace_config/trace_config.textproto8
-rw-r--r--tests/FlickerTests/IME/Android.bp3
-rw-r--r--tests/FlickerTests/IME/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/Notification/Android.bp1
-rw-r--r--tests/FlickerTests/Notification/trace_config/trace_config.textproto8
-rw-r--r--tests/FlickerTests/QuickSwitch/Android.bp1
-rw-r--r--tests/FlickerTests/QuickSwitch/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/Rotation/Android.bp1
-rw-r--r--tests/FlickerTests/Rotation/trace_config/trace_config.textproto6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt5
97 files changed, 2962 insertions, 702 deletions
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 7e142a5ddd7b..83db4cbb7e43 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1433,10 +1433,10 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
synchronized (mLock) {
- // Exclude jobs scheduled on behalf of this app for now because SyncManager
+ // Exclude jobs scheduled on behalf of this app because SyncManager
// and other job proxy agents may not know to reschedule the job properly
// after force stop.
- // TODO(209852664): determine how to best handle syncs & other proxied jobs
+ // Proxied jobs will not be allowed to run if the source app is stopped.
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
/* includeSchedulingApp */ true, /* includeSourceApp */ false,
JobParameters.STOP_REASON_USER,
@@ -1448,7 +1448,9 @@ public class JobSchedulerService extends com.android.server.SystemService
}
};
- private String getPackageName(Intent intent) {
+ /** Returns the package name stored in the intent's data. */
+ @Nullable
+ public static String getPackageName(Intent intent) {
Uri uri = intent.getData();
String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
return pkg;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index cd3ba6b9e13e..4aadc903ba23 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -17,18 +17,26 @@
package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.getPackageName;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
@@ -50,6 +58,8 @@ import java.util.function.Predicate;
*
* - the uid-active boolean state expressed by the AppStateTracker. Jobs in 'active'
* uids are inherently eligible to run jobs regardless of the uid's standby bucket.
+ *
+ * - the app's stopped state
*/
public final class BackgroundJobsController extends StateController {
private static final String TAG = "JobScheduler.Background";
@@ -63,9 +73,48 @@ public final class BackgroundJobsController extends StateController {
private final ActivityManagerInternal mActivityManagerInternal;
private final AppStateTrackerImpl mAppStateTracker;
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Boolean> mPackageStoppedState = new SparseArrayMap<>();
private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String pkgName = getPackageName(intent);
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final String action = intent.getAction();
+ if (pkgUid == -1) {
+ Slog.e(TAG, "Didn't get package UID in intent (" + action + ")");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Got " + action + " for " + pkgUid + "/" + pkgName);
+ }
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_RESTARTED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+ updateJobRestrictionsForUidLocked(pkgUid, false);
+ }
+ }
+ break;
+
+ case Intent.ACTION_PACKAGE_UNSTOPPED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.FALSE);
+ updateJobRestrictionsLocked(pkgUid, UNKNOWN);
+ }
+ }
+ break;
+ }
+ }
+ };
+
public BackgroundJobsController(JobSchedulerService service) {
super(service);
@@ -73,11 +122,18 @@ public final class BackgroundJobsController extends StateController {
LocalServices.getService(ActivityManagerInternal.class));
mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
LocalServices.getService(AppStateTracker.class));
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
@Override
public void startTrackingLocked() {
mAppStateTracker.addListener(mForceAppStandbyListener);
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, filter, null, null);
}
@Override
@@ -99,11 +155,45 @@ public final class BackgroundJobsController extends StateController {
}
@Override
+ public void onAppRemovedLocked(String packageName, int uid) {
+ mPackageStoppedState.delete(uid, packageName);
+ }
+
+ @Override
+ public void onUserRemovedLocked(int userId) {
+ for (int u = mPackageStoppedState.numMaps() - 1; u >= 0; --u) {
+ final int uid = mPackageStoppedState.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mPackageStoppedState.deleteAt(u);
+ }
+ }
+ }
+
+ @Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
+ pw.println("Aconfig flags:");
+ pw.increaseIndent();
+ pw.print(android.content.pm.Flags.FLAG_STAY_STOPPED,
+ android.content.pm.Flags.stayStopped());
+ pw.println();
+ pw.decreaseIndent();
+ pw.println();
+
mAppStateTracker.dump(pw);
pw.println();
+ pw.println("Stopped packages:");
+ pw.increaseIndent();
+ mPackageStoppedState.forEach((uid, pkgName, isStopped) -> {
+ pw.print(uid);
+ pw.print(":");
+ pw.print(pkgName);
+ pw.print("=");
+ pw.println(isStopped);
+ });
+ pw.println();
+
mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
final int uid = jobStatus.getSourceUid();
final String sourcePkg = jobStatus.getSourcePackageName();
@@ -205,14 +295,34 @@ public final class BackgroundJobsController extends StateController {
}
}
+ private boolean isPackageStopped(String packageName, int uid) {
+ if (mPackageStoppedState.contains(uid, packageName)) {
+ return mPackageStoppedState.get(uid, packageName);
+ }
+ final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+ mPackageStoppedState.add(uid, packageName, isStopped);
+ return isStopped;
+ }
+
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, final long nowElapsed,
int activeState) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean isUserBgRestricted =
- !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
- && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName);
+ final boolean isSourcePkgStopped =
+ isPackageStopped(jobStatus.getSourcePackageName(), jobStatus.getSourceUid());
+ final boolean isCallingPkgStopped;
+ if (!jobStatus.isProxyJob()) {
+ isCallingPkgStopped = isSourcePkgStopped;
+ } else {
+ isCallingPkgStopped =
+ isPackageStopped(jobStatus.getCallingPackageName(), jobStatus.getUid());
+ }
+ final boolean isStopped = android.content.pm.Flags.stayStopped()
+ && (isCallingPkgStopped || isSourcePkgStopped);
+ final boolean isUserBgRestricted = isStopped
+ || (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
// If a job started with the foreground flag, it'll cause the UID to stay active
// and thus cause areJobsRestricted() to always return false, so if
// areJobsRestricted() returns false and the app is BG restricted and not TOP,
@@ -233,7 +343,8 @@ public final class BackgroundJobsController extends StateController {
&& isUserBgRestricted
&& mService.getUidProcState(uid)
> ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- final boolean canRun = !shouldStopImmediately
+ // Don't let jobs (including proxied jobs) run if the app is in the stopped state.
+ final boolean canRun = !isStopped && !shouldStopImmediately
&& !mAppStateTracker.areJobsRestricted(
uid, packageName, jobStatus.canRunInBatterySaver());
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 b74806494a60..d1f575ef40c8 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
@@ -1102,6 +1102,12 @@ public final class JobStatus {
return job.getService();
}
+ /** Return the package name of the app that scheduled the job. */
+ public String getCallingPackageName() {
+ return job.getService().getPackageName();
+ }
+
+ /** Return the package name of the app on whose behalf the job was scheduled. */
public String getSourcePackageName() {
return sourcePackageName;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 076fddf234b9..98a78cfaa38c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1162,6 +1162,13 @@ package android.content.pm {
field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
}
+ public static final class UserProperties.Builder {
+ ctor public UserProperties.Builder();
+ method @NonNull public android.content.pm.UserProperties build();
+ method @NonNull public android.content.pm.UserProperties.Builder setShowInQuietMode(int);
+ method @NonNull public android.content.pm.UserProperties.Builder setShowInSharingSurfaces(int);
+ }
+
}
package android.content.res {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5c6c49a18caa..23a5d4d20a2e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2805,7 +2805,7 @@ public class Intent implements Parcelable, Cloneable {
* and the package in the stopped state cannot self-start for any reason unless there's an
* explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED}
* broadcast is sent when such an explicit process start occurs and the package is taken
- * out of the stopped state.
+ * out of the stopped state. The data contains the name of the package.
* </p>
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 445ca0c98416..56e8291f25e9 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -1076,6 +1076,8 @@ public final class UserProperties implements Parcelable {
* Intended for building default values (and so all properties are present in the built object).
* @hide
*/
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
public static final class Builder {
// UserProperties fields and their default values.
private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
@@ -1099,54 +1101,82 @@ public final class UserProperties implements Parcelable {
private boolean mDeleteAppWithParent = false;
private boolean mAlwaysVisible = false;
+ /**
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @TestApi
+ public Builder() {}
+
+ /** @hide */
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
return this;
}
+ /** @hide */
public Builder setStartWithParent(boolean startWithParent) {
mStartWithParent = startWithParent;
return this;
}
- /** Sets the value for {@link #mShowInSettings} */
+ /** Sets the value for {@link #mShowInSettings}
+ * @hide
+ */
public Builder setShowInSettings(@ShowInSettings int showInSettings) {
mShowInSettings = showInSettings;
return this;
}
- /** Sets the value for {@link #mShowInQuietMode} */
+ /** Sets the value for {@link #mShowInQuietMode}
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public Builder setShowInQuietMode(@ShowInQuietMode int showInQuietMode) {
mShowInQuietMode = showInQuietMode;
return this;
}
- /** Sets the value for {@link #mShowInSharingSurfaces}. */
+ /** Sets the value for {@link #mShowInSharingSurfaces}.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public Builder setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) {
mShowInSharingSurfaces = showInSharingSurfaces;
return this;
}
- /** Sets the value for {@link #mInheritDevicePolicy}*/
+ /** Sets the value for {@link #mInheritDevicePolicy}
+ * @hide
+ */
public Builder setInheritDevicePolicy(
@InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
mInheritDevicePolicy = inheritRestrictionsDevicePolicy;
return this;
}
+ /** @hide */
public Builder setUseParentsContacts(boolean useParentsContacts) {
mUseParentsContacts = useParentsContacts;
return this;
}
- /** Sets the value for {@link #mUpdateCrossProfileIntentFiltersOnOTA} */
+ /** Sets the value for {@link #mUpdateCrossProfileIntentFiltersOnOTA}
+ * @hide
+ */
public Builder setUpdateCrossProfileIntentFiltersOnOTA(boolean
updateCrossProfileIntentFiltersOnOTA) {
mUpdateCrossProfileIntentFiltersOnOTA = updateCrossProfileIntentFiltersOnOTA;
return this;
}
- /** Sets the value for {@link #mCrossProfileIntentFilterAccessControl} */
+ /** Sets the value for {@link #mCrossProfileIntentFilterAccessControl}
+ * @hide
+ */
public Builder setCrossProfileIntentFilterAccessControl(
@CrossProfileIntentFilterAccessControlLevel int
crossProfileIntentFilterAccessControl) {
@@ -1154,24 +1184,30 @@ public final class UserProperties implements Parcelable {
return this;
}
- /** Sets the value for {@link #mCrossProfileIntentResolutionStrategy} */
+ /** Sets the value for {@link #mCrossProfileIntentResolutionStrategy}
+ * @hide
+ */
public Builder setCrossProfileIntentResolutionStrategy(@CrossProfileIntentResolutionStrategy
int crossProfileIntentResolutionStrategy) {
mCrossProfileIntentResolutionStrategy = crossProfileIntentResolutionStrategy;
return this;
}
+ /** @hide */
public Builder setMediaSharedWithParent(boolean mediaSharedWithParent) {
mMediaSharedWithParent = mediaSharedWithParent;
return this;
}
+ /** @hide */
public Builder setCredentialShareableWithParent(boolean credentialShareableWithParent) {
mCredentialShareableWithParent = credentialShareableWithParent;
return this;
}
- /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */
+ /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode}
+ * @hide
+ */
public Builder setAuthAlwaysRequiredToDisableQuietMode(
boolean authAlwaysRequiredToDisableQuietMode) {
mAuthAlwaysRequiredToDisableQuietMode =
@@ -1179,19 +1215,28 @@ public final class UserProperties implements Parcelable {
return this;
}
- /** Sets the value for {@link #mDeleteAppWithParent}*/
+ /** Sets the value for {@link #mDeleteAppWithParent}
+ * @hide
+ */
public Builder setDeleteAppWithParent(boolean deleteAppWithParent) {
mDeleteAppWithParent = deleteAppWithParent;
return this;
}
- /** Sets the value for {@link #mAlwaysVisible}*/
+ /** Sets the value for {@link #mAlwaysVisible}
+ * @hide
+ */
public Builder setAlwaysVisible(boolean alwaysVisible) {
mAlwaysVisible = alwaysVisible;
return this;
}
- /** Builds a UserProperties object with *all* values populated. */
+ /** Builds a UserProperties object with *all* values populated.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index dae3202b2043..025aac962fb9 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -53,10 +53,10 @@ public final class FileIntegrityManager {
* verification, although the app APIs are only made available to apps in a later SDK version.
* Only when this method returns true, the other fs-verity APIs in the same class can succeed.
*
- * <p>The app may not need this method and just call the other APIs (i.e. {@link
- * #setupFsVerity(File)} and {@link #getFsVerityDigest(File)}) normally and handle any failure.
- * If some app feature really depends on fs-verity (e.g. protecting integrity of a large file
- * download), an early check of support status may avoid any cost if it is to fail late.
+ * <p>The app may not need this method and just call the other APIs normally and handle any
+ * failure. If some app feature really depends on fs-verity (e.g. protecting integrity of a
+ * large file download), an early check of support status may avoid any cost if it is to fail
+ * late.
*
* <p>Note: for historical reasons this is named {@code isApkVeritySupported()} instead of
* {@code isFsVeritySupported()}. It has also been available since API level 30, predating the
diff --git a/core/java/android/view/HdrRenderState.java b/core/java/android/view/HdrRenderState.java
new file mode 100644
index 000000000000..2fbbf48dff77
--- /dev/null
+++ b/core/java/android/view/HdrRenderState.java
@@ -0,0 +1,121 @@
+/*
+ * 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.view;
+
+import android.os.SystemClock;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.function.Consumer;
+
+/** @hide */
+class HdrRenderState implements Consumer<Display> {
+ // Targeting an animation from 1x to 5x over 400ms means we need to increase by 0.01/ms
+ private static final float TRANSITION_PER_MS = 0.01f;
+
+ private static final boolean FLAG_ANIMATE_ENABLED = Flags.animateHdrTransitions();
+
+ private final ViewRootImpl mViewRoot;
+
+ private boolean mIsListenerRegistered = false;
+ private boolean mUpdateHdrSdrRatioInfo = false;
+ private float mDesiredHdrSdrRatio = 1f;
+ private float mTargetHdrSdrRatio = 1f;
+ private float mRenderHdrSdrRatio = 1f;
+ private float mPreviousRenderRatio = 1f;
+ private long mLastUpdateMillis = -1;
+
+ HdrRenderState(ViewRootImpl viewRoot) {
+ mViewRoot = viewRoot;
+ }
+
+ @Override
+ public void accept(Display display) {
+ forceUpdateHdrSdrRatio();
+ mViewRoot.invalidate();
+ }
+
+ boolean isHdrEnabled() {
+ return mDesiredHdrSdrRatio >= 1.01f;
+ }
+
+ void stopListening() {
+ if (mIsListenerRegistered) {
+ mViewRoot.mDisplay.unregisterHdrSdrRatioChangedListener(this);
+ mIsListenerRegistered = false;
+ }
+ }
+
+ void startListening() {
+ if (isHdrEnabled() && !mIsListenerRegistered && mViewRoot.mDisplay != null) {
+ mViewRoot.mDisplay.registerHdrSdrRatioChangedListener(mViewRoot.mExecutor, this);
+ }
+ }
+
+ /** @return true if something changed, else false */
+ boolean updateForFrame(long frameTimeMillis) {
+ boolean hasUpdate = mUpdateHdrSdrRatioInfo;
+ mUpdateHdrSdrRatioInfo = false;
+ mRenderHdrSdrRatio = mTargetHdrSdrRatio;
+ long timeDelta = Math.max(Math.min(32, frameTimeMillis - mLastUpdateMillis), 8);
+ final float maxStep = timeDelta * TRANSITION_PER_MS;
+ mLastUpdateMillis = frameTimeMillis;
+ if (hasUpdate && FLAG_ANIMATE_ENABLED) {
+ if (mTargetHdrSdrRatio == 1.0f) {
+ mPreviousRenderRatio = mTargetHdrSdrRatio;
+ } else {
+ float delta = mTargetHdrSdrRatio - mPreviousRenderRatio;
+ if (delta > maxStep) {
+ mRenderHdrSdrRatio = mPreviousRenderRatio + maxStep;
+ mUpdateHdrSdrRatioInfo = true;
+ mViewRoot.invalidate();
+ }
+ mPreviousRenderRatio = mRenderHdrSdrRatio;
+ }
+ }
+ return hasUpdate;
+ }
+
+ float getDesiredHdrSdrRatio() {
+ return mDesiredHdrSdrRatio;
+ }
+
+ float getRenderHdrSdrRatio() {
+ return mRenderHdrSdrRatio;
+ }
+
+ void forceUpdateHdrSdrRatio() {
+ mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mViewRoot.mDisplay.getHdrSdrRatio());
+ mUpdateHdrSdrRatioInfo = true;
+ }
+
+ void setDesiredHdrSdrRatio(float desiredRatio) {
+ mLastUpdateMillis = SystemClock.uptimeMillis();
+ // TODO: When decreasing the desired ratio we need to animate it downwards
+ if (desiredRatio != mDesiredHdrSdrRatio) {
+ mDesiredHdrSdrRatio = desiredRatio;
+ forceUpdateHdrSdrRatio();
+ mViewRoot.invalidate();
+
+ if (isHdrEnabled()) {
+ startListening();
+ } else {
+ stopListening();
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 870ec4bb6969..1530aa78d73d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -735,10 +735,7 @@ public final class ViewRootImpl implements ViewParent,
private BLASTBufferQueue mBlastBufferQueue;
- private boolean mUpdateHdrSdrRatioInfo = false;
- private float mDesiredHdrSdrRatio = 1f;
- private float mRenderHdrSdrRatio = 1f;
- private Consumer<Display> mHdrSdrRatioChangedListener = null;
+ private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
/**
* Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
@@ -1813,7 +1810,7 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mThreadedRenderer = renderer;
renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());
- updateRenderHdrSdrRatio();
+ mHdrRenderState.forceUpdateHdrSdrRatio();
updateForceDarkMode();
mAttachInfo.mHardwareAccelerated = true;
mAttachInfo.mHardwareAccelerationRequested = true;
@@ -2156,9 +2153,7 @@ public final class ViewRootImpl implements ViewParent,
private void updateInternalDisplay(int displayId, Resources resources) {
final Display preferredDisplay =
ResourcesManager.getInstance().getAdjustedDisplay(displayId, resources);
- if (mHdrSdrRatioChangedListener != null && mDisplay != null) {
- mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
- }
+ mHdrRenderState.stopListening();
if (preferredDisplay == null) {
// Fallback to use default display.
Slog.w(TAG, "Cannot get desired display with Id: " + displayId);
@@ -2167,9 +2162,7 @@ public final class ViewRootImpl implements ViewParent,
} else {
mDisplay = preferredDisplay;
}
- if (mHdrSdrRatioChangedListener != null && mDisplay != null) {
- mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
- }
+ mHdrRenderState.startListening();
mContext.updateDisplay(mDisplay.getDisplayId());
}
@@ -5154,11 +5147,12 @@ public final class ViewRootImpl implements ViewParent,
useAsyncReport = true;
- if (mUpdateHdrSdrRatioInfo) {
- mUpdateHdrSdrRatioInfo = false;
+ if (mHdrRenderState.updateForFrame(mAttachInfo.mDrawingTime)) {
+ final float renderRatio = mHdrRenderState.getRenderHdrSdrRatio();
applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness(
- getSurfaceControl(), mRenderHdrSdrRatio, mDesiredHdrSdrRatio));
- mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio);
+ getSurfaceControl(), renderRatio,
+ mHdrRenderState.getDesiredHdrSdrRatio()));
+ mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(renderRatio);
}
if (activeSyncGroup != null) {
@@ -5769,11 +5763,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private void updateRenderHdrSdrRatio() {
- mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
- mUpdateHdrSdrRatioInfo = true;
- }
-
private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode,
float desiredRatio) {
if (mAttachInfo.mThreadedRenderer == null) {
@@ -5793,22 +5782,8 @@ public final class ViewRootImpl implements ViewParent,
if (desiredRatio == 0 || desiredRatio > automaticRatio) {
desiredRatio = automaticRatio;
}
- if (desiredRatio != mDesiredHdrSdrRatio) {
- mDesiredHdrSdrRatio = desiredRatio;
- updateRenderHdrSdrRatio();
- invalidate();
- if (mDesiredHdrSdrRatio < 1.01f) {
- mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
- mHdrSdrRatioChangedListener = null;
- } else {
- mHdrSdrRatioChangedListener = display -> {
- updateRenderHdrSdrRatio();
- invalidate();
- };
- mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
- }
- }
+ mHdrRenderState.setDesiredHdrSdrRatio(desiredRatio);
}
@Override
@@ -6428,7 +6403,7 @@ public final class ViewRootImpl implements ViewParent,
}
final ViewRootHandler mHandler = new ViewRootHandler();
- private final Executor mExecutor = (Runnable r) -> {
+ final Executor mExecutor = (Runnable r) -> {
mHandler.post(r);
};
@@ -8764,7 +8739,7 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
}
- updateRenderHdrSdrRatio();
+ mHdrRenderState.forceUpdateHdrSdrRatio();
if (mPreviousTransformHint != transformHint) {
mPreviousTransformHint = transformHint;
dispatchTransformHintChanged(transformHint);
@@ -9312,9 +9287,7 @@ public final class ViewRootImpl implements ViewParent,
private void destroyHardwareRenderer() {
ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
- if (mHdrSdrRatioChangedListener != null) {
- mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
- }
+ mHdrRenderState.stopListening();
if (hardwareRenderer != null) {
if (mHardwareRendererObserver != null) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 3dbe65ef4180..a38092a21178 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -185,16 +185,31 @@ public final class AccessibilityManager {
/**
* Annotations for the shortcut type.
+ * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
+ // LINT.IfChange(shortcut_type_intdef)
ACCESSIBILITY_BUTTON,
ACCESSIBILITY_SHORTCUT_KEY
+ // LINT.ThenChange(:shortcut_type_array)
})
public @interface ShortcutType {}
/**
+ * Used for iterating through {@link ShortcutType}.
+ * <p>Note: Keep in sync with {@link ShortcutType}.</p>
+ * @hide
+ */
+ public static final int[] SHORTCUT_TYPES = {
+ // LINT.IfChange(shortcut_type_array)
+ ACCESSIBILITY_BUTTON,
+ ACCESSIBILITY_SHORTCUT_KEY,
+ // LINT.ThenChange(:shortcut_type_intdef)
+ };
+
+ /**
* Annotations for content flag of UI.
* @hide
*/
@@ -914,6 +929,28 @@ public final class AccessibilityManager {
}
/**
+ * Returns whether the user must be shown the AccessibilityService warning dialog
+ * before the AccessibilityService (or any shortcut for the service) can be enabled.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public boolean isAccessibilityServiceWarningRequired(@NonNull AccessibilityServiceInfo info) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return true;
+ }
+ }
+ try {
+ return service.isAccessibilityServiceWarningRequired(info);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while checking isAccessibilityServiceWarningRequired: ", re);
+ return true;
+ }
+ }
+
+ /**
* Registers an {@link AccessibilityStateChangeListener} for changes in
* the global accessibility state of the system. Equivalent to calling
* {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index f741080c57a8..9c04c27d189a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -128,6 +128,9 @@ interface IAccessibilityManager {
boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info);
+
parcelable WindowTransformationSpec {
float[] transformationMatrix;
MagnificationSpec magnificationSpec;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index b29967888312..75ea08e5450b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,17 +17,17 @@ flag {
}
flag {
+ name: "cleanup_accessibility_warning_dialog"
namespace: "accessibility"
- name: "collection_info_item_counts"
- description: "Fields for total items and the number of important for accessibility items in a collection"
- bug: "302376158"
+ description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog."
+ bug: "303511250"
}
flag {
- name: "deduplicate_accessibility_warning_dialog"
namespace: "accessibility"
- description: "Removes duplicate definition of the accessibility warning dialog."
- bug: "303511250"
+ name: "collection_info_item_counts"
+ description: "Fields for total items and the number of important for accessibility items in a collection"
+ bug: "302376158"
}
flag {
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 14ec14bf7cfc..966161fd642a 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -31,6 +31,7 @@ import static android.view.contentcapture.ContentCaptureHelper.getSanitizedStrin
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
+import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -209,14 +210,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
binder = resultData.getBinder(EXTRA_BINDER);
if (binder == null) {
Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
- mainSession.mHandler.post(() -> mainSession.resetSession(
+ mainSession.runOnContentCaptureThread(() -> mainSession.resetSession(
STATE_DISABLED | STATE_INTERNAL_ERROR));
return;
}
} else {
binder = null;
}
- mainSession.mHandler.post(() ->
+ mainSession.runOnContentCaptureThread(() ->
mainSession.onSessionStarted(resultCode, binder));
}
}
@@ -256,7 +257,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
*/
void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags) {
- runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(
+ () -> startImpl(token, shareableActivityToken, component, flags));
+ } else {
+ // Preserve the control arm behaviour.
+ startImpl(token, shareableActivityToken, component, flags);
+ }
}
private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@@ -613,7 +620,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
public void flush(@FlushReason int reason) {
- runOnContentCaptureThread(() -> flushImpl(reason));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(() -> flushImpl(reason));
+ } else {
+ // Preserve the control arm behaviour.
+ flushImpl(reason);
+ }
}
private void flushImpl(@FlushReason int reason) {
@@ -904,7 +916,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/** public because is also used by ViewRootImpl */
public void notifyContentCaptureEvents(
@NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
- runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+ } else {
+ // Preserve the control arm behaviour.
+ notifyContentCaptureEventsImpl(contentCaptureEvents);
+ }
}
private void notifyContentCaptureEventsImpl(
@@ -1076,19 +1093,30 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
* </p>
*/
private void runOnContentCaptureThread(@NonNull Runnable r) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(r);
+ if (runOnBackgroundThreadEnabled()) {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ mHandler.post(r);
+ } else {
+ r.run();
+ }
} else {
- r.run();
+ // Preserve the control arm behaviour to always post to the handler.
+ mHandler.post(r);
}
}
private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
- if (!mHandler.getLooper().isCurrentThread()) {
+ if (runOnBackgroundThreadEnabled()) {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ mHandler.removeMessages(what);
+ mHandler.post(r);
+ } else {
+ r.run();
+ }
+ } else {
+ // Preserve the control arm behaviour to always post to the handler.
mHandler.removeMessages(what);
mHandler.post(r);
- } else {
- r.run();
}
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index d4eccd458e35..7d06e3f5a7bc 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.Flags;
import android.widget.AdapterView;
@@ -114,18 +115,39 @@ public class AccessibilityShortcutChooserActivity extends Activity {
private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
- if (!target.isShortcutEnabled()) {
- if (target instanceof AccessibilityServiceTarget
- || target instanceof AccessibilityActivityTarget) {
+ if (Flags.cleanupAccessibilityWarningDialog()) {
+ if (target instanceof AccessibilityServiceTarget serviceTarget) {
if (sendRestrictedDialogIntentIfNeeded(target)) {
return;
}
+ final AccessibilityManager am = getSystemService(AccessibilityManager.class);
+ if (am.isAccessibilityServiceWarningRequired(
+ serviceTarget.getAccessibilityServiceInfo())) {
+ showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
+ position, mTargetAdapter);
+ return;
+ }
+ }
+ if (target instanceof AccessibilityActivityTarget activityTarget) {
+ if (!activityTarget.isShortcutEnabled()
+ && sendRestrictedDialogIntentIfNeeded(activityTarget)) {
+ return;
+ }
}
+ } else {
+ if (!target.isShortcutEnabled()) {
+ if (target instanceof AccessibilityServiceTarget
+ || target instanceof AccessibilityActivityTarget) {
+ if (sendRestrictedDialogIntentIfNeeded(target)) {
+ return;
+ }
+ }
- if (target instanceof AccessibilityServiceTarget) {
- showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
- position, mTargetAdapter);
- return;
+ if (target instanceof AccessibilityServiceTarget) {
+ showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
+ position, mTargetAdapter);
+ return;
+ }
}
}
@@ -156,7 +178,7 @@ public class AccessibilityShortcutChooserActivity extends Activity {
return;
}
- if (Flags.deduplicateAccessibilityWarningDialog()) {
+ if (Flags.cleanupAccessibilityWarningDialog()) {
mPermissionDialog = AccessibilityServiceWarning
.createAccessibilityServiceWarningDialog(context,
serviceTarget.getAccessibilityServiceInfo(),
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 088b57feb200..6374e5df3307 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -18,7 +18,6 @@ package com.android.internal.accessibility;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.action.ViewActions.doubleClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
@@ -85,10 +84,13 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tests for {@link AccessibilityShortcutChooserActivity}.
@@ -151,6 +153,8 @@ public class AccessibilityShortcutChooserActivityTest {
when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
anyInt())).thenReturn(new ParceledListSlice<>(
Collections.singletonList(mAccessibilityServiceInfo)));
+ when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
+ .thenReturn(true);
when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
anyString(), anyInt(), anyInt())).thenReturn(true);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
@@ -169,7 +173,7 @@ public class AccessibilityShortcutChooserActivityTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ @RequiresFlagsDisabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() {
launchActivity();
openShortcutsList();
@@ -183,7 +187,7 @@ public class AccessibilityShortcutChooserActivityTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_allow_rowChecked() {
launchActivity();
openShortcutsList();
@@ -197,7 +201,7 @@ public class AccessibilityShortcutChooserActivityTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_deny_rowNotChecked() {
launchActivity();
openShortcutsList();
@@ -211,7 +215,7 @@ public class AccessibilityShortcutChooserActivityTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
launchActivity();
openShortcutsList();
@@ -227,6 +231,59 @@ public class AccessibilityShortcutChooserActivityTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception {
+ when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
+ .thenReturn(false);
+ launchActivity();
+ openShortcutsList();
+
+ // Clicking the test service should not show a permission dialog window,
+ assertThat(mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(
+ Until.newWindow(), UI_TIMEOUT_MS)).isFalse();
+ // and should become checked.
+ assertThat(mDevice.findObject(By.checked(true))).isNotNull();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired()
+ throws Exception {
+ when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
+ .thenReturn(false);
+ when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
+ eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
+ // This test class mocks AccessibilityManagerService, so the restricted dialog window
+ // will not actually appear and therefore cannot be used for a wait Until.newWindow().
+ // To still allow smart waiting in this test we can instead set up the mocked method
+ // to update an atomic boolean and wait for that to be set.
+ final Object waitObject = new Object();
+ final AtomicBoolean calledSendRestrictedDialogIntent = new AtomicBoolean(false);
+ Mockito.doAnswer((Answer<Void>) invocation -> {
+ synchronized (waitObject) {
+ calledSendRestrictedDialogIntent.set(true);
+ waitObject.notify();
+ }
+ return null;
+ }).when(mAccessibilityManagerService).sendRestrictedDialogIntent(
+ eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
+ launchActivity();
+ openShortcutsList();
+
+ mDevice.findObject(By.text(TEST_LABEL)).click();
+ final long timeout = System.currentTimeMillis() + UI_TIMEOUT_MS;
+ synchronized (waitObject) {
+ while (!calledSendRestrictedDialogIntent.get() &&
+ (System.currentTimeMillis() < timeout)) {
+ waitObject.wait(timeout - System.currentTimeMillis());
+ }
+ }
+
+ assertThat(calledSendRestrictedDialogIntent.get()).isTrue();
+ assertThat(mDevice.findObject(By.checked(true))).isNull();
+ }
+
+ @Test
public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
throws Exception {
when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
@@ -329,7 +386,7 @@ public class AccessibilityShortcutChooserActivityTest {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (Flags.deduplicateAccessibilityWarningDialog()) {
+ if (Flags.cleanupAccessibilityWarningDialog()) {
// Setting the Theme is necessary here for the dialog to use the proper style
// resources as designated in its layout XML.
setTheme(R.style.Theme_DeviceDefault_DayNight);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
index b76dd51d3f2b..24aab6192c50 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -58,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@RequiresFlagsEnabled(
- android.view.accessibility.Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public class AccessibilityServiceWarningTest {
private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService";
private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index d5fab441cd46..fe4980a9eb16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -554,6 +554,11 @@ public class PipTransition extends PipTransitionController {
}
}
}
+ // if overlay is present remove it immediately, as exit transition came before it faded out
+ if (mPipOrganizer.mSwipePipToHomeOverlay != null) {
+ startTransaction.remove(mPipOrganizer.mSwipePipToHomeOverlay);
+ clearSwipePipToHomeOverlay();
+ }
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: No window of exiting PIP is found. Can't play expand animation", TAG);
@@ -1007,7 +1012,6 @@ public class PipTransition extends PipTransitionController {
// the overlay to the final PIP task.
startTransaction.reparent(swipePipToHomeOverlay, leash)
.setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
- mPipOrganizer.mSwipePipToHomeOverlay = null;
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1029,7 +1033,7 @@ public class PipTransition extends PipTransitionController {
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- null /* callback */, false /* withStartDelay */);
+ this::clearSwipePipToHomeOverlay /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1173,6 +1177,10 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.updateMenuBounds(destinationBounds);
}
+ private void clearSwipePipToHomeOverlay() {
+ mPipOrganizer.mSwipePipToHomeOverlay = null;
+ }
+
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index d7b306c3be23..03170a326890 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -57,10 +57,13 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+
+ tapl.enableBlockTimeout(true)
}
@Test
open fun enterSplitScreenByDragFromAllApps() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
@@ -72,5 +75,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 8134fddd40e5..479d01ddaeb9 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -59,10 +59,13 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+
+ tapl.enableBlockTimeout(true)
}
@Test
open fun enterSplitScreenByDragFromShortcut() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
@@ -83,6 +86,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardwon() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 3417744f13a5..625c56bc4a4c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -54,6 +54,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ tapl.enableBlockTimeout(true)
+
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
@@ -61,6 +63,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun enterSplitScreenByDragFromTaskbar() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
@@ -71,6 +74,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 394864ad9d4d..5c43cbdb3832 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -23,6 +23,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -42,8 +43,10 @@ abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker:
setup {
tapl.goHome()
primaryApp.launchViaIntent(wmHelper)
+ tapl.enableBlockTimeout(true)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
@@ -57,6 +60,11 @@ abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker:
Assume.assumeTrue(tapl.isTablet)
}
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 3b3be84f9841..15ad0c12c49a 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -23,6 +23,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -42,13 +43,20 @@ abstract class EnterSplitScreenByDragFromShortcutBenchmark(
Assume.assumeTrue(tapl.isTablet)
}
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
+ }
+
protected val thisTransition: FlickerBuilder.() -> Unit = {
setup {
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
+ tapl.enableBlockTimeout(true)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index eff355987cc0..ca8adb1fcb38 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -23,6 +23,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -44,6 +45,7 @@ abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker:
primaryApp.launchViaIntent(wmHelper)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
@@ -54,6 +56,12 @@ abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker:
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
+ tapl.enableBlockTimeout(true)
+ }
+
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
}
companion object {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 78a64795967a..ca119757e816 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,6 +1,13 @@
package: "com.android.graphics.hwui.flags"
flag {
+ name: "matrix_44"
+ namespace: "core_graphics"
+ description: "API for 4x4 matrix and related canvas functions"
+ bug: "280116960"
+}
+
+flag {
name: "limited_hdr"
namespace: "core_graphics"
description: "API to enable apps to restrict the amount of HDR headroom that is used"
@@ -41,3 +48,10 @@ flag {
description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
bug: "292545615"
}
+
+flag {
+ name: "animate_hdr_transitions"
+ namespace: "core_graphics"
+ description: "Automatically animate all changes in HDR headroom"
+ bug: "314810174"
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index a5998faa68ad..db69b8bdf42b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -60,14 +60,15 @@ fun ModalBottomSheet(
sheetContent = sheetContent,
sheetShape = EntryShape.TopRoundedCorner,
) {}
- LaunchedEffect(state.currentValue) {
+ LaunchedEffect(state.currentValue, state.targetValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
if (isInitialRender) {
onInitialRenderComplete()
scope.launch { state.show() }
- } else {
+ } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
+ // Only dismiss ui when the motion is downwards
onDismiss()
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 658b45f68c92..2986504b7dbc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -17,11 +17,13 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
/**
* Defines the behavior of the [SceneTransitionLayout] when a scrollable component is scrolled.
@@ -32,8 +34,9 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
*/
enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
/**
- * During scene transitions, scroll events are consumed by the [SceneTransitionLayout] instead
- * of the scrollable component.
+ * During scene transitions, if we are within
+ * [SceneTransitionLayoutImpl.transitionInterceptionThreshold], the [SceneTransitionLayout]
+ * consumes scroll events instead of the scrollable component.
*/
DuringTransitionBetweenScenes(canStartOnPostFling = false),
@@ -72,21 +75,101 @@ internal fun Modifier.nestedScrollToScene(
orientation: Orientation,
startBehavior: NestedScrollBehavior,
endBehavior: NestedScrollBehavior,
-): Modifier = composed {
- val connection =
- remember(layoutImpl, orientation, startBehavior, endBehavior) {
+) =
+ this then
+ NestedScrollToSceneElement(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+private data class NestedScrollToSceneElement(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val orientation: Orientation,
+ private val startBehavior: NestedScrollBehavior,
+ private val endBehavior: NestedScrollBehavior,
+) : ModifierNodeElement<NestedScrollToSceneNode>() {
+ override fun create() =
+ NestedScrollToSceneNode(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+ override fun update(node: NestedScrollToSceneNode) {
+ node.update(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "nestedScrollToScene"
+ properties["layoutImpl"] = layoutImpl
+ properties["orientation"] = orientation
+ properties["startBehavior"] = startBehavior
+ properties["endBehavior"] = endBehavior
+ }
+}
+
+private class NestedScrollToSceneNode(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+ startBehavior: NestedScrollBehavior,
+ endBehavior: NestedScrollBehavior,
+) : DelegatingNode() {
+ private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
+ scenePriorityNestedScrollConnection(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+ private var nestedScrollNode: DelegatableNode =
+ nestedScrollModifierNode(
+ connection = priorityNestedScrollConnection,
+ dispatcher = null,
+ )
+
+ override fun onAttach() {
+ delegate(nestedScrollNode)
+ }
+
+ override fun onDetach() {
+ // Make sure we reset the scroll connection when this modifier is removed from composition
+ priorityNestedScrollConnection.reset()
+ }
+
+ fun update(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+ startBehavior: NestedScrollBehavior,
+ endBehavior: NestedScrollBehavior,
+ ) {
+ // Clean up the old nested scroll connection
+ priorityNestedScrollConnection.reset()
+ undelegate(nestedScrollNode)
+
+ // Create a new nested scroll connection
+ priorityNestedScrollConnection =
scenePriorityNestedScrollConnection(
layoutImpl = layoutImpl,
orientation = orientation,
startBehavior = startBehavior,
- endBehavior = endBehavior
+ endBehavior = endBehavior,
)
- }
-
- // Make sure we reset the scroll connection when this modifier is removed from composition
- DisposableEffect(connection) { onDispose { connection.reset() } }
-
- nestedScroll(connection = connection)
+ nestedScrollNode =
+ nestedScrollModifierNode(
+ connection = priorityNestedScrollConnection,
+ dispatcher = null,
+ )
+ delegate(nestedScrollNode)
+ }
}
private fun scenePriorityNestedScrollConnection(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ea3006f0b502..8896e6e64bd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -83,7 +84,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
underTest =
CommunalEditModeViewModel(
withDeps.communalInteractor,
- shadeViewController,
+ Provider { shadeViewController },
powerManager,
mediaHost,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9bd083501780..7fbcae0d8986 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -84,7 +85,7 @@ class CommunalViewModelTest : SysuiTestCase() {
CommunalViewModel(
withDeps.communalInteractor,
withDeps.tutorialInteractor,
- shadeViewController,
+ Provider { shadeViewController },
powerManager,
mediaHost,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
new file mode 100644
index 000000000000..7497ebdc2978
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.tiles.impl.uimodenight.domain
+
+import android.app.UiModeManager
+import android.content.res.Configuration
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UiModeNightTileDataInteractorTest : SysuiTestCase() {
+ private val configurationController: ConfigurationController =
+ ConfigurationControllerImpl(context)
+ private val batteryController = FakeBatteryController(LeakCheck())
+ private val locationController = FakeLocationController(LeakCheck())
+
+ private lateinit var underTest: UiModeNightTileDataInteractor
+
+ @Mock private lateinit var uiModeManager: UiModeManager
+ @Mock private lateinit var dateFormatUtil: DateFormatUtil
+
+ @Before
+ fun setup() {
+ uiModeManager = mock<UiModeManager>()
+ dateFormatUtil = mock<DateFormatUtil>()
+
+ whenever(uiModeManager.customNightModeStart).thenReturn(LocalTime.MIN)
+ whenever(uiModeManager.customNightModeEnd).thenReturn(LocalTime.MAX)
+
+ underTest =
+ UiModeNightTileDataInteractor(
+ context,
+ configurationController,
+ uiModeManager,
+ batteryController,
+ locationController,
+ dateFormatUtil
+ )
+ }
+
+ @Test
+ fun collectTileDataReadsUiModeManagerNightMode() = runTest {
+ val expectedNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED
+ whenever(uiModeManager.nightMode).thenReturn(expectedNightMode)
+
+ val model by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ assertThat(model).isNotNull()
+ val actualNightMode = model?.uiMode
+ assertThat(actualNightMode).isEqualTo(expectedNightMode)
+ }
+
+ @Test
+ fun collectTileDataReadsUiModeManagerNightModeCustomTypeAndTimes() = runTest {
+ collectLastValue(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ verify(uiModeManager).nightMode
+ verify(uiModeManager).nightModeCustomType
+ verify(uiModeManager).customNightModeStart
+ verify(uiModeManager).customNightModeEnd
+ }
+
+ /** Here, available refers to the tile showing up, not the tile being clickable. */
+ @Test
+ fun isAvailableRegardlessOfPowerSaveModeOn() = runTest {
+ batteryController.setPowerSaveMode(true)
+
+ runCurrent()
+ val availability by collectLastValue(underTest.availability(TEST_USER))
+
+ assertThat(availability).isTrue()
+ }
+
+ @Test
+ fun dataMatchesConfigurationController() = runTest {
+ setUiMode(UI_MODE_NIGHT_NO)
+ val flowValues: List<UiModeNightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ setUiMode(UI_MODE_NIGHT_YES)
+ runCurrent()
+ setUiMode(UI_MODE_NIGHT_NO)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues.map { it.isNightMode }).containsExactly(false, true, false).inOrder()
+ }
+
+ @Test
+ fun dataMatchesBatteryController() = runTest {
+ batteryController.setPowerSaveMode(false)
+ val flowValues: List<UiModeNightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ batteryController.setPowerSaveMode(true)
+ runCurrent()
+ batteryController.setPowerSaveMode(false)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues.map { it.isPowerSave }).containsExactly(false, true, false).inOrder()
+ }
+
+ @Test
+ fun dataMatchesLocationController() = runTest {
+ locationController.setLocationEnabled(false)
+ val flowValues: List<UiModeNightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ locationController.setLocationEnabled(true)
+ runCurrent()
+ locationController.setLocationEnabled(false)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues.map { it.isLocationEnabled })
+ .containsExactly(false, true, false)
+ .inOrder()
+ }
+
+ @Test
+ fun collectTileDataReads24HourFormatFromDateTimeUtil() = runTest {
+ collectLastValue(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+ runCurrent()
+
+ verify(dateFormatUtil).is24HourFormat
+ }
+
+ /**
+ * Use this method to trigger [ConfigurationController.ConfigurationListener.onUiModeChanged]
+ */
+ private fun setUiMode(uiMode: Int) {
+ val config = context.resources.configuration
+ val newConfig = Configuration(config)
+ newConfig.uiMode = uiMode
+
+ /** [underTest] will see this config the next time it creates a model */
+ context.orCreateTestableResources.overrideConfiguration(newConfig)
+
+ /** Trigger updateUiMode callbacks */
+ configurationController.onConfigurationChanged(newConfig)
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
new file mode 100644
index 000000000000..87f50090e58b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -0,0 +1,481 @@
+/*
+ * 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.tiles.impl.uimodenight.domain
+
+import android.app.UiModeManager
+import android.text.TextUtils
+import android.view.View
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.uimodenight.UiModeNightTileModelHelper.createModel
+import com.android.systemui.qs.tiles.impl.uimodenight.qsUiModeNightTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import kotlin.reflect.KClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UiModeNightTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileConfig = kosmos.qsUiModeNightTileConfig
+
+ private val mapper by lazy {
+ UiModeNightTileMapper(context.orCreateTestableResources.resources)
+ }
+
+ private fun createUiNightModeTileState(
+ iconRes: Int = R.drawable.qs_light_dark_theme_icon_off,
+ label: CharSequence = context.getString(R.string.quick_settings_ui_mode_night_label),
+ activationState: QSTileState.ActivationState = QSTileState.ActivationState.INACTIVE,
+ secondaryLabel: CharSequence? = null,
+ supportedActions: Set<QSTileState.UserAction> =
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription: CharSequence? = null,
+ stateDescription: CharSequence? = null,
+ sideViewIcon: QSTileState.SideViewIcon = QSTileState.SideViewIcon.None,
+ enabledState: QSTileState.EnabledState = QSTileState.EnabledState.ENABLED,
+ expandedAccessibilityClass: KClass<out View>? = Switch::class,
+ ): QSTileState {
+ return QSTileState(
+ { Icon.Resource(iconRes, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ supportedActions,
+ contentDescription,
+ stateDescription,
+ sideViewIcon,
+ enabledState,
+ expandedAccessibilityClass?.qualifiedName
+ )
+ }
+
+ @Test
+ fun mapsEnabledDataToUnavailableStateWhenOnPowerSave() {
+ val inputModel = createModel(nightMode = true, powerSave = true)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedContentDescription =
+ TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel)
+ val expectedState =
+ createUiNightModeTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ secondaryLabel = expectedSecondaryLabel,
+ contentDescription = expectedContentDescription
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsDisabledDataToUnavailableStateWhenOnPowerSave() {
+ val inputModel = createModel(nightMode = false, powerSave = true)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedContentDescription =
+ TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel)
+ val expectedState =
+ createUiNightModeTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ secondaryLabel = expectedSecondaryLabel,
+ contentDescription = expectedContentDescription
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsDisabledDataToInactiveState() {
+ val inputModel = createModel(nightMode = false, powerSave = false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[1]
+ val expectedState =
+ createUiNightModeTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ contentDescription = expectedLabel
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsEnabledDataToActiveState() {
+ val inputModel = createModel(true, false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[2]
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = expectedLabel
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsEnabledDataToOnIconState() {
+ val inputModel = createModel(nightMode = true, powerSave = false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[2]
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = expectedLabel
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsDisabledDataToOffIconState() {
+ val inputModel = createModel(nightMode = false, powerSave = false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[1]
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ contentDescription = expectedLabel
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun supportsClickAndLongClickActionsWhenNotInPowerSaveInNightMode() {
+ val inputModel = createModel(nightMode = true, powerSave = false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[2]
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun supportsOnlyLongClickActionWhenUnavailableInPowerSaveInNightMode() {
+ val inputModel = createModel(nightMode = true, powerSave = true)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedContentDescription =
+ TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ contentDescription = expectedContentDescription,
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun supportsClickAndLongClickActionsWhenNotInPowerSaveNotInNightMode() {
+ val inputModel = createModel(nightMode = false, powerSave = false)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[1]
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun supportsOnlyClickActionWhenUnavailableInPowerSaveNotInNightMode() {
+ val inputModel = createModel(nightMode = false, powerSave = true)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenInPowerSaveMode() {
+ val inputModel = createModel(powerSave = true)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenInNightModeNotInPowerSaveModeLocationEnabledUiModeIsNightAuto() {
+ val inputModel =
+ createModel(
+ nightMode = true,
+ powerSave = false,
+ isLocationEnabled = true,
+ uiMode = UiModeManager.MODE_NIGHT_AUTO
+ )
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_until_sunrise)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenNotInNightModeNotInPowerSaveModeLocationEnableUiModeIsNightAuto() {
+ val inputModel =
+ createModel(
+ nightMode = false,
+ powerSave = false,
+ isLocationEnabled = true,
+ uiMode = UiModeManager.MODE_NIGHT_AUTO
+ )
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_on_at_sunset)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenNotInPowerSaveAndUiModeIsNightYesInNightMode() {
+ val inputModel =
+ createModel(nightMode = true, powerSave = false, uiMode = UiModeManager.MODE_NIGHT_YES)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[2]
+
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenNotInPowerSaveAndUiModeIsNightNoNotInNightMode() {
+ val inputModel =
+ createModel(nightMode = false, powerSave = false, uiMode = UiModeManager.MODE_NIGHT_NO)
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[1]
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenNotInPowerSaveAndUiModeIsUnknownCustomNotInNightMode() {
+ val inputModel =
+ createModel(
+ nightMode = false,
+ powerSave = false,
+ uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ )
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[1]
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenNotInPowerSaveAndUiModeIsUnknownCustomInNightMode() {
+ val inputModel =
+ createModel(
+ nightMode = true,
+ powerSave = false,
+ uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ )
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel = context.resources.getStringArray(R.array.tile_states_dark)[2]
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_on,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.ACTIVE,
+ contentDescription = expectedLabel,
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun secondaryLabelCorrectWhenInPowerSaveAndUiModeIsUnknownCustomNotInNightMode() {
+ val inputModel =
+ createModel(
+ nightMode = false,
+ powerSave = true,
+ uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ )
+
+ val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
+
+ val expectedSecondaryLabel =
+ context.getString(R.string.quick_settings_dark_mode_secondary_label_battery_saver)
+ val expectedLabel = context.getString(R.string.quick_settings_ui_mode_night_label)
+ val expectedContentDescription =
+ TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel)
+ val expectedState =
+ createUiNightModeTileState(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ label = expectedLabel,
+ secondaryLabel = expectedSecondaryLabel,
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ contentDescription = expectedContentDescription,
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ )
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..004ec6250e7e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.tiles.impl.uimodenight.domain
+
+import android.app.UiModeManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.uimodenight.UiModeNightTileModelHelper.createModel
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UiModeNightTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+
+ private lateinit var underTest: UiModeNightTileUserActionInteractor
+
+ @Mock private lateinit var uiModeManager: UiModeManager
+
+ @Before
+ fun setup() {
+ uiModeManager = mock<UiModeManager>()
+ underTest =
+ UiModeNightTileUserActionInteractor(
+ EmptyCoroutineContext,
+ uiModeManager,
+ qsTileIntentUserActionHandler
+ )
+ }
+
+ @Test
+ fun handleClickToEnable() = runTest {
+ val stateBeforeClick = false
+
+ underTest.handleInput(QSTileInputTestKtx.click(createModel(stateBeforeClick)))
+
+ verify(uiModeManager).setNightModeActivated(!stateBeforeClick)
+ }
+
+ @Test
+ fun handleClickToDisable() = runTest {
+ val stateBeforeClick = true
+
+ underTest.handleInput(QSTileInputTestKtx.click(createModel(stateBeforeClick)))
+
+ verify(uiModeManager).setNightModeActivated(!stateBeforeClick)
+ }
+
+ @Test
+ fun clickToEnableDoesNothingWhenInPowerSaveInNightMode() = runTest {
+ val isNightMode = true
+ val isPowerSave = true
+
+ underTest.handleInput(QSTileInputTestKtx.click(createModel(isNightMode, isPowerSave)))
+
+ verify(uiModeManager, never()).setNightModeActivated(any())
+ }
+
+ @Test
+ fun clickToEnableDoesNothingWhenInPowerSaveNotInNightMode() = runTest {
+ val isNightMode = false
+ val isPowerSave = true
+
+ underTest.handleInput(QSTileInputTestKtx.click(createModel(isNightMode, isPowerSave)))
+
+ verify(uiModeManager, never()).setNightModeActivated(any())
+ }
+
+ @Test
+ fun handleLongClickNightModeEnabled() = runTest {
+ val isNightMode = true
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(createModel(isNightMode)))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_DARK_THEME_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+
+ @Test
+ fun handleLongClickNightModeDisabled() = runTest {
+ val isNightMode = false
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(createModel(isNightMode)))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_DARK_THEME_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c110de969b57..70be03146e17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -63,6 +63,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.Ignore
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -395,6 +396,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/315130482")
fun deviceGoesToSleep_wakeUp_unlock() =
testScope.runTest {
unlockDevice()
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 8916e427a1d5..fa345c929c25 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -40,16 +40,22 @@
android:popupBackground="@drawable/screenrecord_spinner_background"
android:dropDownWidth="274dp"
android:importantForAccessibility="yes"/>
- <Switch
+ <FrameLayout
+ android:id="@+id/screenrecord_audio_switch_container"
android:layout_width="wrap_content"
- android:minWidth="48dp"
- android:layout_height="48dp"
- android:layout_weight="0"
- android:layout_gravity="end"
- android:id="@+id/screenrecord_audio_switch"
- android:contentDescription="@string/screenrecord_audio_label"
- style="@style/ScreenRecord.Switch"
- android:importantForAccessibility="yes"/>
+ android:layout_height="wrap_content">
+ <Switch
+ android:layout_width="wrap_content"
+ android:minWidth="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="end"
+ android:focusable="false"
+ android:clickable="false"
+ android:id="@+id/screenrecord_audio_switch"
+ android:contentDescription="@string/screenrecord_audio_label"
+ style="@style/ScreenRecord.Switch"
+ android:importantForAccessibility="yes"/>
+ </FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/show_taps"
@@ -75,13 +81,20 @@
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textColor="?android:attr/textColorPrimary"
android:contentDescription="@string/screenrecord_taps_label"/>
- <Switch
+ <FrameLayout
+ android:id="@+id/screenrecord_taps_switch_container"
android:layout_width="wrap_content"
- android:minWidth="48dp"
- android:layout_height="48dp"
- android:id="@+id/screenrecord_taps_switch"
- android:contentDescription="@string/screenrecord_taps_label"
- style="@style/ScreenRecord.Switch"
- android:importantForAccessibility="yes"/>
+ android:layout_height="wrap_content">
+ <Switch
+ android:layout_width="wrap_content"
+ android:minWidth="48dp"
+ android:layout_height="48dp"
+ android:focusable="false"
+ android:clickable="false"
+ android:id="@+id/screenrecord_taps_switch"
+ android:contentDescription="@string/screenrecord_taps_label"
+ style="@style/ScreenRecord.Switch"
+ android:importantForAccessibility="yes"/>
+ </FrameLayout>
</LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index bed42833a1d4..333fc194b288 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -24,13 +24,14 @@ import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.shade.ShadeViewController
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
- private val shadeViewController: ShadeViewController,
+ private val shadeViewController: Provider<ShadeViewController>,
private val powerManager: PowerManager,
val mediaHost: MediaHost,
) {
@@ -48,7 +49,7 @@ abstract class BaseCommunalViewModel(
fun onOuterTouch(motionEvent: MotionEvent) {
// Forward the touch to the shade so that basic gestures like swipe up/down for
// shade/bouncer work.
- shadeViewController.handleExternalTouch(motionEvent)
+ shadeViewController.get().handleExternalTouch(motionEvent)
}
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b6843c529180..c82e00038b34 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -25,6 +25,7 @@ import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
/** The view model for communal hub in edit mode. */
@@ -33,7 +34,7 @@ class CommunalEditModeViewModel
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
- shadeViewController: ShadeViewController,
+ shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7dcdb9ea4f0..abf198637911 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -39,7 +40,7 @@ class CommunalViewModel
constructor(
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
- shadeViewController: ShadeViewController,
+ shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 017dac200431..20da00ee3daf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -81,6 +82,7 @@ constructor(
private val interactionJankMonitor: InteractionJankMonitor,
private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
private val vibratorHelper: VibratorHelper,
+ private val falsingManager: FalsingManager,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -155,6 +157,7 @@ constructor(
interactionJankMonitor,
deviceEntryHapticsInteractor,
vibratorHelper,
+ falsingManager,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2d6c0e1c13b2..b51edab6dfe8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -157,6 +157,9 @@ interface KeyguardRepository {
val lastDozeTapToWakePosition: StateFlow<Point?>
+ /** Last point that [KeyguardRootView] was tapped */
+ val lastRootViewTapPosition: MutableStateFlow<Point?>
+
/** Observable for the [StatusBarState] */
val statusBarState: StateFlow<StatusBarState>
@@ -418,6 +421,8 @@ constructor(
_lastDozeTapToWakePosition.value = position
}
+ override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
+
override val isDreamingWithOverlay: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index b8c392591494..702386d3b498 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -171,6 +171,9 @@ constructor(
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+ /** Last point that [KeyguardRootView] view was tapped */
+ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
+
/** Whether the primary bouncer is showing or not. */
val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
@@ -304,6 +307,10 @@ constructor(
repository.setClockShouldBeCentered(shouldBeCentered)
}
+ fun setLastRootViewTapPosition(point: Point?) {
+ repository.lastRootViewTapPosition.value = point
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 02c6f33ffd01..e603ead463f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.ui.binder
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.DrawableRes
+import android.annotation.SuppressLint
+import android.graphics.Point
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -47,6 +49,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -73,6 +76,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardRootViewBinder {
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: ViewGroup,
@@ -87,6 +91,7 @@ object KeyguardRootViewBinder {
interactionJankMonitor: InteractionJankMonitor?,
deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
vibratorHelper: VibratorHelper?,
+ falsingManager: FalsingManager?,
): DisposableHandle {
var onLayoutChangeListener: OnLayoutChange? = null
val childViews = mutableMapOf<Int, View>()
@@ -94,6 +99,16 @@ object KeyguardRootViewBinder {
val burnInLayerId = R.id.burn_in_layer
val aodNotificationIconContainerId = R.id.aod_notification_icon_container
val largeClockId = R.id.lockscreen_clock_view_large
+
+ if (keyguardBottomAreaRefactor()) {
+ view.setOnTouchListener { _, event ->
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+ }
+ false
+ }
+ }
+
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 8514225fda90..11e63e76c289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.binder
+import android.graphics.Rect
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -25,6 +26,8 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
@@ -35,12 +38,15 @@ import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
object KeyguardSettingsViewBinder {
fun bind(
parentView: View,
viewModel: KeyguardSettingsMenuViewModel,
+ longPressViewModel: KeyguardLongPressViewModel,
+ rootViewModel: KeyguardRootViewModel,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
@@ -88,6 +94,18 @@ object KeyguardSettingsViewBinder {
}
}
}
+
+ launch {
+ rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ if (view.isVisible) {
+ val hitRect = Rect()
+ view.getHitRect(hitRect)
+ if (!hitRect.contains(point.x, point.y)) {
+ longPressViewModel.onTouchedOutside()
+ }
+ }
+ }
+ }
}
}
return disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4eecfdefa663..03e45fdbe75f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -376,6 +376,7 @@ constructor(
null, // jank monitor not required for preview mode
null, // device entry haptics not required preview mode
null, // device entry haptics not required for preview mode
+ null, // falsing manager not required for preview mode
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 9a33f08386a3..4bc2d86e6b54 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -29,15 +29,15 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import androidx.core.view.isVisible
import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.res.R
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -47,6 +47,8 @@ class DefaultSettingsPopupMenuSection
constructor(
@Main private val resources: Resources,
private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
+ private val keyguardLongPressViewModel: KeyguardLongPressViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
private val vibratorHelper: VibratorHelper,
private val activityStarter: ActivityStarter,
) : KeyguardSection() {
@@ -73,6 +75,8 @@ constructor(
KeyguardSettingsViewBinder.bind(
constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
keyguardSettingsMenuViewModel,
+ keyguardLongPressViewModel,
+ keyguardRootViewModel,
vibratorHelper,
activityStarter,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 97ddbb033648..4588e02df10e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.graphics.Point
import android.util.MathUtils
import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
@@ -101,6 +102,9 @@ constructor(
val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
+ /** Last point that the root view was tapped */
+ val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
+
/** the shared notification container bounds *on the lockscreen* */
val notificationBounds: StateFlow<NotificationContainerBounds> =
keyguardInteractor.notificationContainerBounds
@@ -262,4 +266,8 @@ constructor(
}
.toAnimatedValueFlow()
}
+
+ fun setRootViewLastTapPosition(point: Point) {
+ keyguardInteractor.setLastRootViewTapPosition(point)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
new file mode 100644
index 000000000000..3f30c75a6b6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.tiles.impl.uimodenight.domain
+
+import android.app.UiModeManager
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+/** Maps [UiModeNightTileModel] to [QSTileState]. */
+class UiModeNightTileMapper @Inject constructor(@Main private val resources: Resources) :
+ QSTileDataToStateMapper<UiModeNightTileModel> {
+ companion object {
+ val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+ val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+ }
+ override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
+ with(data) {
+ QSTileState.build(resources, config.uiConfig) {
+ var shouldSetSecondaryLabel = false
+
+ if (isPowerSave) {
+ secondaryLabel =
+ resources.getString(
+ R.string.quick_settings_dark_mode_secondary_label_battery_saver
+ )
+ } else if (uiMode == UiModeManager.MODE_NIGHT_AUTO && isLocationEnabled) {
+ secondaryLabel =
+ resources.getString(
+ if (isNightMode)
+ R.string.quick_settings_dark_mode_secondary_label_until_sunrise
+ else R.string.quick_settings_dark_mode_secondary_label_on_at_sunset
+ )
+ } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) {
+ if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE) {
+ val time: LocalTime =
+ if (isNightMode) {
+ customNightModeEnd
+ } else {
+ customNightModeStart
+ }
+
+ val formatter: DateTimeFormatter =
+ if (is24HourFormat) formatter24Hour else formatter12Hour
+
+ secondaryLabel =
+ resources.getString(
+ if (isNightMode)
+ R.string.quick_settings_dark_mode_secondary_label_until
+ else R.string.quick_settings_dark_mode_secondary_label_on_at,
+ formatter.format(time)
+ )
+ } else if (
+ nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME
+ ) {
+ secondaryLabel =
+ resources.getString(
+ if (isNightMode)
+ R.string
+ .quick_settings_dark_mode_secondary_label_until_bedtime_ends
+ else R.string.quick_settings_dark_mode_secondary_label_on_at_bedtime
+ )
+ } else {
+ secondaryLabel = null // undefined type of nightModeCustomType
+ shouldSetSecondaryLabel = true
+ }
+ } else {
+ secondaryLabel = null
+ shouldSetSecondaryLabel = true
+ }
+
+ contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ if (isPowerSave) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ if (shouldSetSecondaryLabel)
+ secondaryLabel = resources.getStringArray(R.array.tile_states_dark)[0]
+ } else {
+ activationState =
+ if (isNightMode) QSTileState.ActivationState.ACTIVE
+ else QSTileState.ActivationState.INACTIVE
+
+ if (shouldSetSecondaryLabel) {
+ secondaryLabel =
+ if (activationState == QSTileState.ActivationState.INACTIVE)
+ resources.getStringArray(R.array.tile_states_dark)[1]
+ else resources.getStringArray(R.array.tile_states_dark)[2]
+ }
+ }
+
+ val iconRes =
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_light_dark_theme_icon_on
+ else R.drawable.qs_light_dark_theme_icon_off
+ val iconResource = Icon.Resource(iconRes, null)
+ icon = { iconResource }
+
+ supportedActions =
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
new file mode 100644
index 000000000000..c928e8af17fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.tiles.impl.uimodenight.domain.interactor
+
+import android.app.UiModeManager
+import android.content.Context
+import android.content.res.Configuration
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.time.DateFormatUtil
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes ui mode night state changes providing the [UiModeNightTileModel]. */
+class UiModeNightTileDataInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val configurationController: ConfigurationController,
+ private val uiModeManager: UiModeManager,
+ private val batteryController: BatteryController,
+ private val locationController: LocationController,
+ private val dateFormatUtil: DateFormatUtil,
+) : QSTileDataInteractor<UiModeNightTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<UiModeNightTileModel> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ // send initial state
+ trySend(createModel())
+
+ val configurationCallback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ trySend(createModel())
+ }
+ }
+ configurationController.addCallback(configurationCallback)
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onPowerSaveChanged(isPowerSave: Boolean) {
+ trySend(createModel())
+ }
+ }
+ batteryController.addCallback(batteryCallback)
+
+ val locationCallback =
+ object : LocationController.LocationChangeCallback {
+ override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+ trySend(createModel())
+ }
+ }
+ locationController.addCallback(locationCallback)
+
+ awaitClose {
+ configurationController.removeCallback(configurationCallback)
+ batteryController.removeCallback(batteryCallback)
+ locationController.removeCallback(locationCallback)
+ }
+ }
+
+ private fun createModel(): UiModeNightTileModel {
+ val uiMode = uiModeManager.nightMode
+ val nightMode =
+ (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
+ Configuration.UI_MODE_NIGHT_YES
+ val powerSave = batteryController.isPowerSave
+ val locationEnabled = locationController.isLocationEnabled
+ val nightModeCustomType = uiModeManager.nightModeCustomType
+ val use24HourFormat = dateFormatUtil.is24HourFormat
+ val customNightModeEnd = uiModeManager.customNightModeEnd
+ val customNightModeStart = uiModeManager.customNightModeStart
+
+ return UiModeNightTileModel(
+ uiMode,
+ nightMode,
+ powerSave,
+ locationEnabled,
+ nightModeCustomType,
+ use24HourFormat,
+ customNightModeEnd,
+ customNightModeStart
+ )
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt
new file mode 100644
index 000000000000..00d7a629f5be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.tiles.impl.uimodenight.domain.interactor
+
+import android.app.UiModeManager
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles ui mode night tile clicks. */
+class UiModeNightTileUserActionInteractor
+@Inject
+constructor(
+ @Background private val backgroundContext: CoroutineContext,
+ private val uiModeManager: UiModeManager,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<UiModeNightTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<UiModeNightTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (!input.data.isPowerSave) {
+ withContext(backgroundContext) {
+ uiModeManager.setNightModeActivated(!input.data.isNightMode)
+ }
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_DARK_THEME_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/model/UiModeNightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/model/UiModeNightTileModel.kt
new file mode 100644
index 000000000000..4fa1306d988d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/model/UiModeNightTileModel.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.tiles.impl.uimodenight.domain.model
+
+import java.time.LocalTime
+
+/**
+ * UiModeNight tile model. Quick Settings tile for: Night Mode / Dark Theme / Dark Mode.
+ *
+ * @param isNightMode is true when the NightMode is enabled;
+ */
+data class UiModeNightTileModel(
+ val uiMode: Int,
+ val isNightMode: Boolean,
+ val isPowerSave: Boolean,
+ val isLocationEnabled: Boolean,
+ val nightModeCustomType: Int,
+ val is24HourFormat: Boolean,
+ val customNightModeEnd: LocalTime,
+ val customNightModeStart: LocalTime
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index 10d51a59e44c..3eb26f498921 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -28,6 +28,7 @@ import android.view.MotionEvent.ACTION_MOVE
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
+import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.AdapterView
import android.widget.ArrayAdapter
@@ -64,10 +65,13 @@ class ScreenRecordPermissionDialogDelegate(
mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
R.color.screenrecord_icon_color
- ), SystemUIDialog.Delegate {
+ ),
+ SystemUIDialog.Delegate {
private lateinit var tapsSwitch: Switch
+ private lateinit var tapsSwitchContainer: ViewGroup
private lateinit var tapsView: View
private lateinit var audioSwitch: Switch
+ private lateinit var audioSwitchContainer: ViewGroup
private lateinit var options: Spinner
override fun createDialog(): SystemUIDialog {
@@ -114,12 +118,17 @@ class ScreenRecordPermissionDialogDelegate(
private fun initRecordOptionsView() {
audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch)
tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch)
+ audioSwitchContainer = dialog.requireViewById(R.id.screenrecord_audio_switch_container)
+ tapsSwitchContainer = dialog.requireViewById(R.id.screenrecord_taps_switch_container)
// Add these listeners so that the switch only responds to movement
// within its target region, to meet accessibility requirements
audioSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
tapsSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
+ audioSwitchContainer.setOnClickListener { audioSwitch.toggle() }
+ tapsSwitchContainer.setOnClickListener { tapsSwitch.toggle() }
+
tapsView = dialog.requireViewById(R.id.show_taps)
updateTapsViewVisibility()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 93bc96022292..af6da3fb6e51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -146,7 +146,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* When you just need a dialog, call this.
*/
public SystemUIDialog create() {
- return create(new DialogDelegate<>(){});
+ return create(new DialogDelegate<>(){}, mContext);
}
/**
@@ -155,13 +155,18 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
*
* When you need to customize the dialog, pass it a delegate.
*/
+ public SystemUIDialog create(Delegate delegate, Context context) {
+ return create((DialogDelegate<SystemUIDialog>) delegate, context);
+ }
+
public SystemUIDialog create(Delegate delegate) {
- return create((DialogDelegate<SystemUIDialog>) delegate);
+ return create(delegate, mContext);
}
- private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate) {
+ private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate,
+ Context context) {
return new SystemUIDialog(
- mContext,
+ context,
DEFAULT_THEME,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
mFeatureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 0f2da2d09633..087e100e9b33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -38,6 +38,10 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.UiModeNightTileMapper
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
@@ -64,6 +68,7 @@ interface PolicyModule {
const val FLASHLIGHT_TILE_SPEC = "flashlight"
const val LOCATION_TILE_SPEC = "location"
const val ALARM_TILE_SPEC = "alarm"
+ const val UIMODENIGHT_TILE_SPEC = "dark"
/** Inject flashlight config */
@Provides
@@ -160,6 +165,38 @@ interface PolicyModule {
stateInteractor,
mapper,
)
+
+ /** Inject uimodenight config */
+ @Provides
+ @IntoMap
+ @StringKey(UIMODENIGHT_TILE_SPEC)
+ fun provideUiModeNightTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(UIMODENIGHT_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_light_dark_theme_icon_off,
+ labelRes = R.string.quick_settings_ui_mode_night_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject uimodenight into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(UIMODENIGHT_TILE_SPEC)
+ fun provideUiModeNightTileViewModel(
+ factory: QSTileViewModelFactory.Static<UiModeNightTileModel>,
+ mapper: UiModeNightTileMapper,
+ stateInteractor: UiModeNightTileDataInteractor,
+ userActionInteractor: UiModeNightTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(UIMODENIGHT_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index d6e6f3fc56b1..bd698ab8ad5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -157,6 +157,7 @@ public class CsdWarningDialog extends SystemUIDialog
if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REPEATED_5X) {
// only show a notification in case we reached 500% of dose
show5XNotification();
+ dismissCsdDialog();
return;
}
super.show();
@@ -217,6 +218,10 @@ public class CsdWarningDialog extends SystemUIDialog
@Override
public void onDismiss(DialogInterface unused) {
+ dismissCsdDialog();
+ }
+
+ private void dismissCsdDialog() {
try {
mContext.unregisterReceiver(mReceiver);
} catch (IllegalArgumentException e) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 81a7bec52bb5..0e7c6625264c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -119,6 +119,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
+ override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileKosmos.kt
new file mode 100644
index 000000000000..f0e5807ca515
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.tiles.impl.uimodenight
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsUiModeNightTileConfig by
+ Kosmos.Fixture { PolicyModule.provideUiModeNightTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileModelHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileModelHelper.kt
new file mode 100644
index 000000000000..1fe18e3f4d8e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/uimodenight/UiModeNightTileModelHelper.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.qs.tiles.impl.uimodenight
+
+import android.content.res.Configuration
+import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import java.time.LocalTime
+
+object UiModeNightTileModelHelper {
+
+ const val DEFAULT_NIGHT_MODE_CUSTOM_TYPE = 0
+ val defaultCustomNightEnd: LocalTime = LocalTime.MAX
+ val defaultCustomNightStart: LocalTime = LocalTime.MIN
+
+ fun createModel(
+ nightMode: Boolean = false,
+ powerSave: Boolean = false,
+ uiMode: Int = Configuration.UI_MODE_NIGHT_NO,
+ isLocationEnabled: Boolean = true,
+ nighModeCustomType: Int = DEFAULT_NIGHT_MODE_CUSTOM_TYPE,
+ is24HourFormat: Boolean = false,
+ customNightModeEnd: LocalTime = defaultCustomNightEnd,
+ customNightModeStart: LocalTime = defaultCustomNightStart
+ ): UiModeNightTileModel {
+ return UiModeNightTileModel(
+ uiMode,
+ nightMode,
+ powerSave,
+ isLocationEnabled,
+ nighModeCustomType,
+ is24HourFormat,
+ customNightModeEnd,
+ customNightModeStart
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 209cac6688a2..5ae033c9870d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -22,11 +22,16 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
implements BatteryController {
private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
+ private boolean mPowerSaveMode = false;
+
+ private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
public FakeBatteryController(LeakCheck test) {
super(test, "battery");
@@ -44,12 +49,18 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
@Override
public void setPowerSaveMode(boolean powerSave) {
-
+ mPowerSaveMode = powerSave;
+ for (BatteryStateChangeCallback callback: mCallbacks) {
+ callback.onPowerSaveChanged(powerSave);
+ }
}
+ /**
+ * Note: this method ignores the View argument
+ */
@Override
public void setPowerSaveMode(boolean powerSave, View view) {
-
+ setPowerSaveMode(powerSave);
}
@Override
@@ -59,7 +70,7 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
@Override
public boolean isPowerSave() {
- return false;
+ return mPowerSaveMode;
}
@Override
@@ -79,4 +90,14 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
public void setWirelessCharging(boolean wirelessCharging) {
mWirelessCharging = wirelessCharging;
}
+
+ @Override
+ public void addCallback(BatteryStateChangeCallback listener) {
+ mCallbacks.add(listener);
+ }
+
+ @Override
+ public void removeCallback(BatteryStateChangeCallback listener) {
+ mCallbacks.remove(listener);
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
index 3c6327514f24..442d15b7bc95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -26,6 +26,7 @@ public class FakeLocationController extends BaseLeakChecker<LocationChangeCallba
implements LocationController {
private final List<LocationChangeCallback> mCallbacks = new ArrayList<>();
+ private boolean mLocationEnabled = false;
public FakeLocationController(LeakCheck test) {
super(test, "location");
@@ -38,13 +39,14 @@ public class FakeLocationController extends BaseLeakChecker<LocationChangeCallba
@Override
public boolean isLocationEnabled() {
- return false;
+ return mLocationEnabled;
}
@Override
public boolean setLocationEnabled(boolean enabled) {
+ mLocationEnabled = enabled;
mCallbacks.forEach(callback -> callback.onLocationSettingsChanged(enabled));
- return false;
+ return true;
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 5bffe80a5c69..440e99632c86 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4406,6 +4406,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
+ public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
+ mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ // Warning is not required if the service is already enabled.
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ if (userState.getEnabledServicesLocked().contains(info.getComponentName())) {
+ return false;
+ }
+ }
+ // Warning is not required if the service is already assigned to a shortcut.
+ for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
+ if (getAccessibilityShortcutTargets(shortcutType).contains(
+ info.getComponentName().flattenToString())) {
+ return false;
+ }
+ }
+ // Warning is required by default.
+ return true;
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8ed3fd696bda..b4cf34e00c53 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -201,6 +201,7 @@ java_library_static {
"biometrics_flags_lib",
"am_flags_lib",
"com_android_wm_shell_flags_lib",
+ "com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
],
javac_shard_size: 50,
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index eb3ec2444210..05d07ae761c1 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1464,15 +1464,17 @@ public class BinaryTransparencyService extends SystemService {
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
if (android.security.Flags.binaryTransparencySepolicyHash()) {
- byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
- "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
- String sepolicyHashEncoded = null;
- if (sepolicyHash != null) {
- sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
- Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
- }
- FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
- sepolicyHashEncoded, mVbmetaDigest);
+ IoThread.getExecutor().execute(() -> {
+ byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
+ "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
+ String sepolicyHashEncoded = null;
+ if (sepolicyHash != null) {
+ sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
+ Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
+ sepolicyHashEncoded, mVbmetaDigest);
+ });
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5f1a7e7e8123..71916843fe0b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -241,6 +241,7 @@ import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.ServiceRecord.ShortFgsInfo;
import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
+import com.android.server.utils.AnrTimer;
import com.android.server.wm.ActivityServiceConnectionsHolder;
import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 469f209eb9b5..2d687de2f734 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -475,6 +475,7 @@ import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.utils.AnrTimer;
import com.android.server.utils.PriorityDump;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ad499911f84a..2cac7a020005 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -88,6 +88,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.utils.AnrTimer;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 2ed079ab0c62..d9e8dddddae4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -9,14 +9,6 @@ flag {
}
flag {
- name: "anr_timer_service_enabled"
- namespace: "system_performance"
- is_fixed_read_only: true
- description: "Feature flag for the ANR timer service"
- bug: "282428924"
-}
-
-flag {
name: "fgs_abuse_detection"
namespace: "backstage_power"
description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)."
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index b91e633bd3de..ffdab7dfbfa4 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -21,6 +21,8 @@ import static android.media.AudioSystem.DEVICE_NONE;
import static android.media.AudioSystem.isBluetoothDevice;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioDeviceAttributes;
@@ -31,13 +33,16 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Objects;
/**
* Class representing all devices that were previously or are currently connected. Data is
* persisted in {@link android.provider.Settings.Secure}
*/
-/*package*/ final class AdiDeviceState {
+@VisibleForTesting(visibility = PACKAGE)
+public final class AdiDeviceState {
private static final String TAG = "AS.AdiDeviceState";
private static final String SETTING_FIELD_SEPARATOR = ",";
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 98b210f29db4..e54bf64df09e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -31,6 +31,8 @@ import static android.media.AudioSystem.isBluetoothScoOutDevice;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
@@ -347,7 +349,8 @@ public class AudioDeviceInventory {
* @return the found {@link AdiDeviceState} or {@code null} otherwise.
*/
@Nullable
- AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
+ @VisibleForTesting(visibility = PACKAGE)
+ public AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
Set<Integer> deviceSet;
if (isBluetoothA2dpOutDevice(deviceType)) {
deviceSet = DEVICE_OUT_ALL_A2DP_SET;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8cec24d1bbb5..f7b7aaa60a35 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -39,6 +39,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
@@ -6785,7 +6786,8 @@ public class AudioService extends IAudioService.Stub
return mContentResolver;
}
- /*package*/ SettingsAdapter getSettings() {
+ @VisibleForTesting(visibility = PACKAGE)
+ public SettingsAdapter getSettings() {
return mSettings;
}
@@ -11250,7 +11252,9 @@ public class AudioService extends IAudioService.Stub
return mDeviceBroker.isBluetoothAudioDeviceCategoryFixed(address);
}
- /*package*/void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
+ /** Update the sound dose and spatializer state based on the new AdiDeviceState. */
+ @VisibleForTesting(visibility = PACKAGE)
+ public void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
if (deviceState == null) {
return;
}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index cd3f0f0ca5b2..1da7f0c059b0 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -19,7 +19,6 @@ package com.android.server.content;
import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
-import android.content.pm.PackageManagerInternal;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
@@ -29,7 +28,6 @@ import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
public class SyncJobService extends JobService {
private static final String TAG = "SyncManager";
@@ -99,20 +97,6 @@ public class SyncJobService extends JobService {
return true;
}
- // TODO(b/209852664): remove this logic from here once it's added within JobScheduler.
- // JobScheduler should not call onStartJob for syncs whose source packages are stopped.
- // Until JS adds the relevant logic, this is a temporary solution to keep deferring syncs
- // for packages in the stopped state.
- if (android.content.pm.Flags.stayStopped()) {
- if (LocalServices.getService(PackageManagerInternal.class)
- .isPackageStopped(op.owningPackage, op.target.userId)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Slog.d(TAG, "Skipping sync for force-stopped package: " + op.owningPackage);
- }
- return false;
- }
- }
-
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
synchronized (sLock) {
final int jobId = params.getJobId();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 90c406357fd8..df9e7417054b 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2908,46 +2908,69 @@ class MediaRouter2ServiceImpl {
if (service == null) {
return;
}
- List<RouterRecord> activeRouterRecords = Collections.emptyList();
+ List<RouterRecord> activeRouterRecords;
List<RouterRecord> allRouterRecords = getRouterRecords();
- List<ManagerRecord> managerRecords = getManagerRecords();
-
- boolean isManagerScanning = false;
- if (Flags.disableScreenOffBroadcastReceiver()
- || service.mPowerManager.isInteractive()) {
- isManagerScanning = managerRecords.stream().anyMatch(manager ->
- manager.mIsScanning && service.mActivityManager
- .getPackageImportance(manager.mOwnerPackageName)
- <= sPackageImportanceForScanning);
-
- if (isManagerScanning) {
- activeRouterRecords = allRouterRecords;
- } else {
- activeRouterRecords =
- allRouterRecords.stream()
- .filter(
- record ->
- service.mActivityManager.getPackageImportance(
- record.mPackageName)
- <= sPackageImportanceForScanning)
- .collect(Collectors.toList());
- }
+
+ boolean areManagersScanning = areManagersScanning(service, getManagerRecords());
+
+ if (areManagersScanning) {
+ activeRouterRecords = allRouterRecords;
+ } else {
+ activeRouterRecords = getIndividuallyActiveRouters(service, allRouterRecords);
+ }
+
+ updateManagerScanningForProviders(areManagersScanning);
+
+ Set<String> activelyScanningPackages = new HashSet<>();
+ RouteDiscoveryPreference newPreference =
+ buildCompositeDiscoveryPreference(
+ activeRouterRecords, areManagersScanning, activelyScanningPackages);
+
+ if (updateScanningOnUserRecord(service, activelyScanningPackages, newPreference)) {
+ updateDiscoveryPreferenceForProviders(activelyScanningPackages);
}
+ }
+ private void updateDiscoveryPreferenceForProviders(Set<String> activelyScanningPackages) {
for (MediaRoute2Provider provider : mRouteProviders) {
- if (provider instanceof MediaRoute2ProviderServiceProxy) {
- ((MediaRoute2ProviderServiceProxy) provider)
- .setManagerScanning(isManagerScanning);
+ provider.updateDiscoveryPreference(
+ activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
+ }
+ }
+
+ private boolean updateScanningOnUserRecord(
+ MediaRouter2ServiceImpl service,
+ Set<String> activelyScanningPackages,
+ RouteDiscoveryPreference newPreference) {
+ synchronized (service.mLock) {
+ if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
+ && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
+ return false;
}
+ mUserRecord.mCompositeDiscoveryPreference = newPreference;
+ mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
}
+ return true;
+ }
- // Build a composite RouteDiscoveryPreference that matches all of the routes
- // that match one or more of the individual discovery preferences. It may also
- // match additional routes. The composite RouteDiscoveryPreference can be used
- // to query route providers once to obtain all of the routes of interest, which
- // can be subsequently filtered for the individual discovery preferences.
+ /**
+ * Returns a composite {@link RouteDiscoveryPreference} that aggregates every router
+ * record's individual discovery preference.
+ *
+ * <p>The {@link RouteDiscoveryPreference#shouldPerformActiveScan() active scan value} of
+ * the composite discovery preference is true if one of the router records is actively
+ * scanning or if {@code shouldForceActiveScan} is true.
+ *
+ * <p>The composite RouteDiscoveryPreference is used to query route providers once to obtain
+ * all the routes of interest, which can be subsequently filtered for the individual
+ * discovery preferences.
+ */
+ @NonNull
+ private static RouteDiscoveryPreference buildCompositeDiscoveryPreference(
+ List<RouterRecord> activeRouterRecords,
+ boolean shouldForceActiveScan,
+ Set<String> activelyScanningPackages) {
Set<String> preferredFeatures = new HashSet<>();
- Set<String> activelyScanningPackages = new HashSet<>();
boolean activeScan = false;
for (RouterRecord activeRouterRecord : activeRouterRecords) {
RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
@@ -2957,21 +2980,51 @@ class MediaRouter2ServiceImpl {
activelyScanningPackages.add(activeRouterRecord.mPackageName);
}
}
- RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
- List.copyOf(preferredFeatures), activeScan || isManagerScanning).build();
+ return new RouteDiscoveryPreference.Builder(
+ List.copyOf(preferredFeatures), activeScan || shouldForceActiveScan)
+ .build();
+ }
- synchronized (service.mLock) {
- if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
- && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
- return;
+ private void updateManagerScanningForProviders(boolean isManagerScanning) {
+ for (MediaRoute2Provider provider : mRouteProviders) {
+ if (provider instanceof MediaRoute2ProviderServiceProxy) {
+ ((MediaRoute2ProviderServiceProxy) provider)
+ .setManagerScanning(isManagerScanning);
}
- mUserRecord.mCompositeDiscoveryPreference = newPreference;
- mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
}
- for (MediaRoute2Provider provider : mRouteProviders) {
- provider.updateDiscoveryPreference(
- activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
+ }
+
+ @NonNull
+ private static List<RouterRecord> getIndividuallyActiveRouters(
+ MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
+ if (!Flags.disableScreenOffBroadcastReceiver()
+ && !service.mPowerManager.isInteractive()) {
+ return Collections.emptyList();
}
+
+ return allRouterRecords.stream()
+ .filter(
+ record ->
+ service.mActivityManager.getPackageImportance(
+ record.mPackageName)
+ <= sPackageImportanceForScanning)
+ .collect(Collectors.toList());
+ }
+
+ private static boolean areManagersScanning(
+ MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
+ if (!Flags.disableScreenOffBroadcastReceiver()
+ && !service.mPowerManager.isInteractive()) {
+ return false;
+ }
+
+ return managerRecords.stream()
+ .anyMatch(
+ manager ->
+ manager.mIsScanning
+ && service.mActivityManager.getPackageImportance(
+ manager.mOwnerPackageName)
+ <= sPackageImportanceForScanning);
}
private MediaRoute2Provider findProvider(@Nullable String providerId) {
diff --git a/services/core/java/com/android/server/utils/Android.bp b/services/core/java/com/android/server/utils/Android.bp
new file mode 100644
index 000000000000..3a334bee93ff
--- /dev/null
+++ b/services/core/java/com/android/server/utils/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "com.android.server.utils-aconfig",
+ package: "com.android.server.utils",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.server.utils_aconfig-java",
+ aconfig_declarations: "com.android.server.utils-aconfig",
+}
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 3e17930e3cb9..2b6dffb2b271 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.am;
+package com.android.server.utils;
import static android.text.TextUtils.formatSimple;
@@ -77,7 +77,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*
* @hide
*/
-class AnrTimer<V> {
+public class AnrTimer<V> {
/**
* The log tag.
@@ -568,7 +568,7 @@ class AnrTimer<V> {
* @param label A name for this instance.
* @param extend A flag to indicate if expired timers can be granted extensions.
*/
- AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
+ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
this(handler, what, label, extend, new Injector(handler));
}
@@ -580,7 +580,7 @@ class AnrTimer<V> {
* @param what The "what" parameter for the expiration message.
* @param label A name for this instance.
*/
- AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
+ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
this(handler, what, label, false);
}
@@ -591,7 +591,7 @@ class AnrTimer<V> {
*
* @return true if the service is flag-enabled.
*/
- boolean serviceEnabled() {
+ public boolean serviceEnabled() {
return mFeature.enabled();
}
@@ -856,7 +856,7 @@ class AnrTimer<V> {
* @param timeoutMs The timer timeout, in milliseconds.
* @return true if the timer was successfully created.
*/
- boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
return mFeature.start(arg, pid, uid, timeoutMs);
}
@@ -867,7 +867,7 @@ class AnrTimer<V> {
*
* @return true if the timer was found and was running.
*/
- boolean cancel(@NonNull V arg) {
+ public boolean cancel(@NonNull V arg) {
return mFeature.cancel(arg);
}
@@ -878,7 +878,7 @@ class AnrTimer<V> {
*
* @return true if the timer was found and was expired.
*/
- boolean accept(@NonNull V arg) {
+ public boolean accept(@NonNull V arg) {
return mFeature.accept(arg);
}
@@ -892,7 +892,7 @@ class AnrTimer<V> {
*
* @return true if the timer was found and was expired.
*/
- boolean discard(@NonNull V arg) {
+ public boolean discard(@NonNull V arg) {
return mFeature.discard(arg);
}
@@ -1010,7 +1010,7 @@ class AnrTimer<V> {
/**
* Dumpsys output.
*/
- static void dump(@NonNull PrintWriter pw, boolean verbose) {
+ public static void dump(@NonNull PrintWriter pw, boolean verbose) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
new file mode 100644
index 000000000000..489e21ab06ca
--- /dev/null
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.utils"
+
+flag {
+ name: "anr_timer_service_enabled"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Feature flag for the ANR timer service"
+ bug: "282428924"
+}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4625b4fe07ef..f8b22c97e218 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -614,6 +614,15 @@ public class BackgroundActivityStartController {
== ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
if (callerCanAllow && realCallerCanAllow) {
// Both caller and real caller allow with system defined behavior
+ if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
+ // Will be allowed even with BAL hardening.
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed by caller. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ // return the realCaller result for backwards compatibility
+ return statsLog(resultForRealCaller, state);
+ }
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG,
"With Android 15 BAL hardening this activity start may be blocked"
@@ -632,6 +641,14 @@ public class BackgroundActivityStartController {
}
if (callerCanAllow) {
// Allowed before V by creator
+ if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
+ // Will be allowed even with BAL hardening.
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed by caller. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ return statsLog(resultForCaller, state);
+ }
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG,
"With Android 15 BAL hardening this activity start may be blocked"
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
new file mode 100644
index 000000000000..cdae8c6ab004
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2018 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.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppStateTracker;
+import com.android.server.AppStateTrackerImpl;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundJobsControllerTest {
+ private static final int CALLING_UID = 1000;
+ private static final String CALLING_PACKAGE = "com.test.calling.package";
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_UID = 10001;
+ private static final int ALTERNATE_UID = 12345;
+ private static final String ALTERNATE_SOURCE_PACKAGE = "com.test.alternate.package";
+ private static final int SOURCE_USER_ID = 0;
+
+ private BackgroundJobsController mBackgroundJobsController;
+ private BroadcastReceiver mStoppedReceiver;
+ private JobStore mJobStore;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private AppStateTrackerImpl mAppStateTrackerImpl;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .mockStatic(LocalServices.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ // Called in BackgroundJobsController constructor.
+ doReturn(mock(ActivityManagerInternal.class))
+ .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mAppStateTrackerImpl)
+ .when(() -> LocalServices.getService(AppStateTracker.class));
+ doReturn(mPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
+ when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+ // Called in JobStatus constructor.
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+
+ doReturn(false).when(mAppStateTrackerImpl)
+ .areJobsRestricted(anyInt(), anyString(), anyBoolean());
+ doReturn(true).when(mAppStateTrackerImpl)
+ .isRunAnyInBackgroundAppOpsAllowed(anyInt(), anyString());
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ mBackgroundJobsController = new BackgroundJobsController(mJobSchedulerService);
+ mBackgroundJobsController.startTrackingLocked();
+
+ verify(mContext).registerReceiverAsUser(receiverCaptor.capture(), any(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(Intent.ACTION_PACKAGE_RESTARTED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_UNSTOPPED)),
+ any(), any());
+ mStoppedReceiver = receiverCaptor.getValue();
+
+ // Need to do this since we're using a mock JS and not a real object.
+ doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(SOURCE_UID);
+ doReturn(new ArraySet<>(new String[]{ALTERNATE_SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(ALTERNATE_UID);
+ setPackageUid(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE);
+ setPackageUid(SOURCE_UID, SOURCE_PACKAGE);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setPackageUid(final int uid, final String pkgName) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
+ private void setStoppedState(int uid, String pkgName, boolean stopped) {
+ Intent intent = new Intent(
+ stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, pkgName, null));
+ mStoppedReceiver.onReceive(mContext, intent);
+ }
+
+ private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
+ }
+ }
+
+ private void trackJobs(JobStatus... jobs) {
+ for (JobStatus job : jobs) {
+ mJobStore.add(job);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.maybeStartTrackingJobLocked(job, null);
+ }
+ }
+ }
+
+ private JobInfo.Builder createBaseJobInfoBuilder(String pkgName, int jobId) {
+ final ComponentName cn = spy(new ComponentName(pkgName, "TestBJCJobService"));
+ doReturn("TestBJCJobService").when(cn).flattenToShortString();
+ return new JobInfo.Builder(jobId, cn);
+ }
+
+ private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
+ JobInfo jobInfo) {
+ JobStatus js = JobStatus.createFromJobInfo(
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "BJCTest", testTag);
+ js.serviceProcessName = "testProcess";
+ // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+ js.setStandbyBucket(FREQUENT_INDEX);
+ return js;
+ }
+
+ @Test
+ public void testStopped_disabled() {
+ mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+
+ @Test
+ public void testStopped_enabled() {
+ mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertFalse(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertFalse(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 800350a7d326..57c3a1d5f364 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -852,6 +853,53 @@ public class AccessibilityManagerServiceTest {
assertThat(lockState.get()).containsExactly(false);
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
+ public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
+ mockManageAccessibilityGranted(mTestableContext);
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.setComponentName(COMPONENT_NAME);
+
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
+ public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
+ mockManageAccessibilityGranted(mTestableContext);
+ final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+ info_a.setComponentName(COMPONENT_NAME);
+ final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+ info_b.setComponentName(new ComponentName("package_b", "class_b"));
+ final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mEnabledServices.clear();
+ userState.mEnabledServices.add(info_b.getComponentName());
+
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue();
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
+ public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
+ mockManageAccessibilityGranted(mTestableContext);
+ final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+ info_a.setComponentName(new ComponentName("package_a", "class_a"));
+ final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+ info_b.setComponentName(new ComponentName("package_b", "class_b"));
+ final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo();
+ info_c.setComponentName(new ComponentName("package_c", "class_c"));
+ final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mAccessibilityButtonTargets.clear();
+ userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
+ userState.mAccessibilityShortcutKeyTargets.clear();
+ userState.mAccessibilityShortcutKeyTargets.add(info_c.getComponentName().flattenToString());
+
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue();
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isFalse();
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
+ }
+
// Single package intents can trigger multiple PackageMonitor callbacks.
// Collect the state of the lock in a set, since tests only care if calls
// were all locked or all unlocked.
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
deleted file mode 100644
index 44d676052352..000000000000
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ /dev/null
@@ -1,389 +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 com.android.server.am;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.Presubmit;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Build/Install/Run:
- * atest FrameworksServicesTests:AnrTimerTest
- */
-@SmallTest
-@Presubmit
-public class AnrTimerTest {
-
- /**
- * A handler that allows control over when to dispatch messages and callbacks. Because most
- * Handler methods are final, the only thing this handler can intercept is sending messages.
- * This handler allows unit tests to be written without a need to sleep (which leads to flaky
- * tests).
- *
- * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}.
- */
- static class TestHandler extends Handler {
-
- private boolean mImmediate = true;
- private ArrayList<Message> mQueuedMessages = new ArrayList<>();
-
- ArrayList<Long> mDelays = new ArrayList<>();
-
- TestHandler(Looper looper, Callback callback, boolean immediate) {
- super(looper, callback);
- mImmediate = immediate;
- }
-
- TestHandler(Looper looper, Callback callback) {
- this(looper, callback, true);
- }
-
- /**
- * Override sendMessageAtTime. In immediate mode, the message is immediately dispatched.
- * In non-immediate mode, the message is enqueued to the real handler. In both cases, the
- * original delay is computed by comparing the target dispatch time with 'now'. This
- * computation is prone to errors if the code experiences delays. The computed time is
- * captured in the mDelays list.
- */
- @Override
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- long delay = uptimeMillis - SystemClock.uptimeMillis();
- mDelays.add(delay);
- if (mImmediate) {
- mQueuedMessages.add(msg);
- dispatchQueuedMessages();
- } else {
- super.sendMessageAtTime(msg, uptimeMillis);
- }
- return true;
- }
-
- void setImmediate(boolean immediate) {
- mImmediate = immediate;
- }
-
- /** Dispatch any messages that have been queued on the calling thread. */
- void dispatchQueuedMessages() {
- ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
- mQueuedMessages.clear();
- for (Message msg : messages) {
- dispatchMessage(msg);
- }
- }
-
- /**
- * Compare the captured delays with the input array. The comparison is fuzzy because the
- * captured delay (see sendMessageAtTime) is affected by process delays.
- */
- void verifyDelays(long[] r) {
- final long FUZZ = 10;
- assertEquals(r.length, mDelays.size());
- for (int i = 0; i < mDelays.size(); i++) {
- long t = r[i];
- long v = mDelays.get(i);
- assertTrue(v >= t - FUZZ && v <= t + FUZZ);
- }
- }
- }
-
- private Handler mHandler;
- private CountDownLatch mLatch = null;
- private ArrayList<Message> mMessages;
-
- // The commonly used message timeout key.
- private static final int MSG_TIMEOUT = 1;
-
- @Before
- public void setUp() {
- mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
- mMessages = new ArrayList<>();
- mLatch = new CountDownLatch(1);
- AnrTimer.resetTimerListForHermeticTest();
- }
-
- @After
- public void tearDown() {
- mHandler = null;
- mMessages = null;
- }
-
- // When a timer expires, set the expiration time in the message and add it to the queue.
- private boolean expirationHandler(Message msg) {
- mMessages.add(Message.obtain(msg));
- mLatch.countDown();
- return false;
- }
-
- // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
- // different message instances.
- private static class TestArg {
- final int pid;
- final int uid;
- final int tag;
-
- TestArg(int pid, int uid, int tag) {
- this.pid = pid;
- this.uid = uid;
- this.tag = tag;
- }
- @Override
- public String toString() {
- return String.format("pid=%d uid=%d tag=%d", pid, uid, tag);
- }
- }
-
- /**
- * An instrumented AnrTimer.
- */
- private class TestAnrTimer extends AnrTimer {
- // A local copy of 'what'. The field in AnrTimer is private.
- final int mWhat;
-
- TestAnrTimer(Handler h, int key, String tag) {
- super(h, key, tag);
- mWhat = key;
- }
-
- TestAnrTimer() {
- this(mHandler, MSG_TIMEOUT, caller());
- }
-
- TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) {
- super(h, key, tag, extend, injector);
- mWhat = key;
- }
-
- TestAnrTimer(boolean extend, TestInjector injector) {
- this(mHandler, MSG_TIMEOUT, caller(), extend, injector);
- }
-
- // Return the name of method that called the constructor, assuming that this function is
- // called from inside the constructor. The calling method is used to name the AnrTimer
- // instance so that logs are easier to understand.
- private static String caller() {
- final int n = 4;
- StackTraceElement[] stack = Thread.currentThread().getStackTrace();
- if (stack.length < n+1) return "test";
- return stack[n].getMethodName();
- }
-
- boolean start(TestArg arg, long millis) {
- return start(arg, arg.pid, arg.uid, millis);
- }
-
- int what() {
- return mWhat;
- }
- }
-
- private static class TestTracker extends AnrTimer.CpuTracker {
- long index = 0;
- final int skip;
- TestTracker(int skip) {
- this.skip = skip;
- }
- long delay(int pid) {
- return index++ * skip;
- }
- }
-
- private class TestInjector extends AnrTimer.Injector {
- final boolean mImmediate;
- final AnrTimer.CpuTracker mTracker;
- TestHandler mTestHandler;
-
- TestInjector(int skip, boolean immediate) {
- super(mHandler);
- mTracker = new TestTracker(skip);
- mImmediate = immediate;
- }
-
- TestInjector(int skip) {
- this(skip, true);
- }
-
- @Override
- Handler newHandler(Handler.Callback callback) {
- if (mTestHandler == null) {
- mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
- }
- return mTestHandler;
- }
-
- /** Fetch the allocated handle. This does not check for nulls. */
- TestHandler getHandler() {
- return mTestHandler;
- }
-
- /**
- * This override returns the tracker supplied in the constructor. It does not create a
- * new one.
- */
- @Override
- AnrTimer.CpuTracker newTracker() {
- return mTracker;
- }
-
- /** For test purposes, always enable the feature. */
- @Override
- boolean isFeatureEnabled() {
- return true;
- }
- }
-
- // Tests
- // 1. Start a timer and wait for expiration.
- // 2. Start a timer and cancel it. Verify no expiration.
- // 3. Start a timer. Shortly thereafter, restart it. Verify only one expiration.
- // 4. Start a couple of timers. Verify max active timers. Discard one and verify the active
- // count drops by 1. Accept one and verify the active count drops by 1.
-
- @Test
- public void testSimpleTimeout() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(0);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
- // Delivery is immediate but occurs on a different thread.
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the timer is still present.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
-
- // Verify that the timer no longer exists.
- assertFalse(timer.accept(t));
- }
-
- @Test
- public void testCancel() throws Exception {
- // Create an non-immediate TestHandler.
- TestInjector injector = new TestInjector(0, false);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
-
- Handler handler = injector.getHandler();
- assertNotNull(handler);
- assertTrue(handler instanceof TestHandler);
-
- // The tests that follow check for a 'what' of 0 (zero), which is the message key used
- // by AnrTimer internally.
- TestArg t = new TestArg(1, 1, 3);
- assertFalse(handler.hasMessages(0));
- assertTrue(timer.start(t, 100));
- assertTrue(handler.hasMessages(0));
- assertTrue(timer.cancel(t));
- assertFalse(handler.hasMessages(0));
-
- // Verify that no expiration messages were delivered.
- assertEquals(0, mMessages.size());
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testRestart() throws Exception {
- // Create an non-immediate TestHandler.
- TestInjector injector = new TestInjector(0, false);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
-
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 2500));
- assertTrue(timer.start(t, 1000));
-
- // Verify that the test handler saw two timeouts.
- injector.getHandler().verifyDelays(new long[] { 2500, 1000 });
-
- // Verify that there is a single timer. Then cancel it.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.cancel(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testExtendNormal() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(5);
- TestAnrTimer timer = new TestAnrTimer(true, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
-
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms.
- injector.getHandler().verifyDelays(new long[] { 10, 5 });
-
- // Verify that the timer is still present. Then remove it and verify that the list is
- // empty.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testExtendOversize() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(25);
- TestAnrTimer timer = new TestAnrTimer(true, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
-
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms.
- injector.getHandler().verifyDelays(new long[] { 10, 10 });
-
- // Verify that the timer is still present. Then remove it and verify that the list is
- // empty.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 0d58542ab040..cc3c880b8927 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -15,14 +15,28 @@
*/
package com.android.server.audio;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET;
+import static android.media.audio.Flags.automaticBtDeviceType;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
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 static org.mockito.Mockito.when;
+import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
@@ -40,10 +54,10 @@ import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Spy;
@@ -70,6 +84,9 @@ public class AudioDeviceBrokerTest {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
mMockAudioService = mock(AudioService.class);
+ SettingsAdapter mockAdapter = mock(SettingsAdapter.class);
+ when(mMockAudioService.getSettings()).thenReturn(mockAdapter);
+ when(mockAdapter.getSecureStringForUser(any(), any(), anyInt())).thenReturn("");
when(mMockAudioService.getBluetoothContextualVolumeStream())
.thenReturn(AudioSystem.STREAM_MUSIC);
@@ -82,7 +99,6 @@ public class AudioDeviceBrokerTest {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05");
- Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
}
@After
@@ -100,15 +116,14 @@ public class AudioDeviceBrokerTest {
@Test
public void testPostA2dpDeviceConnectionChange() throws Exception {
Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
- Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
+ assertNotNull("invalid null BT device", mFakeBtDevice);
mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource"));
Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
verify(mSpyDevInventory, times(1)).setBluetoothActiveDevice(
- any(AudioDeviceBroker.BtDeviceInfo.class)
- );
+ any(AudioDeviceBroker.BtDeviceInfo.class));
// verify the connection was reported to AudioSystem
checkSingleSystemConnection(mFakeBtDevice);
@@ -212,7 +227,7 @@ public class AudioDeviceBrokerTest {
AudioManager.DEVICE_OUT_SPEAKER, null);
new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null);
- Assert.fail();
+ fail();
} catch (NullPointerException e) { }
}
@@ -228,11 +243,114 @@ public class AudioDeviceBrokerTest {
final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString);
Log.i(TAG, "original:" + devState);
Log.i(TAG, "result :" + result);
- Assert.assertEquals(devState, result);
+ assertEquals(devState, result);
+ }
+
+ @Test
+ public void testIsBluetoothAudioDeviceCategoryFixed() throws Exception {
+ Log.i(TAG, "starting testIsBluetoothAudioDeviceCategoryFixed");
+
+ if (!automaticBtDeviceType()) {
+ Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
+ + "testIsBluetoothAudioDeviceCategoryFixed");
+ return;
+ }
+ assertNotNull("invalid null BT device", mFakeBtDevice);
+
+ final AdiDeviceState devState = new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, mFakeBtDevice.getAddress());
+ doReturn(devState).when(mSpyDevInventory).findBtDeviceStateForAddress(
+ mFakeBtDevice.getAddress(), AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ try {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
+
+ // no metadata set
+ assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes()));
+ assertFalse(
+ mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+ mFakeBtDevice.getAddress()));
+
+ // metadata set
+ assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_HEADSET.getBytes()));
+ assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+ mFakeBtDevice.getAddress()));
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testGetAndUpdateBtAdiDeviceStateCategoryForAddress() throws Exception {
+ Log.i(TAG, "starting testGetAndUpdateBtAdiDeviceStateCategoryForAddress");
+
+ if (!automaticBtDeviceType()) {
+ Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
+ + "testGetAndUpdateBtAdiDeviceStateCategoryForAddress");
+ return;
+ }
+ assertNotNull("invalid null BT device", mFakeBtDevice);
+
+ final AdiDeviceState devState = new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, mFakeBtDevice.getAddress());
+ devState.setAudioDeviceCategory(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
+ doReturn(devState).when(mSpyDevInventory).findBtDeviceStateForAddress(
+ eq(mFakeBtDevice.getAddress()), anyInt());
+ try {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
+
+ // no metadata set
+ assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes()));
+ assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
+ mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+ mFakeBtDevice.getAddress()));
+ verify(mMockAudioService,
+ timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
+ eq(devState));
+
+ // metadata set
+ assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_HEADSET.getBytes()));
+ assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
+ mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+ mFakeBtDevice.getAddress()));
+ verify(mMockAudioService,
+ timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
+ any());
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testAddAudioDeviceWithCategoryInInventoryIfNeeded() throws Exception {
+ Log.i(TAG, "starting testAddAudioDeviceWithCategoryInInventoryIfNeeded");
+
+ if (!automaticBtDeviceType()) {
+ Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
+ + "testAddAudioDeviceWithCategoryInInventoryIfNeeded");
+ return;
+ }
+ assertNotNull("invalid null BT device", mFakeBtDevice);
+
+ mAudioDeviceBroker.addAudioDeviceWithCategoryInInventoryIfNeeded(
+ mFakeBtDevice.getAddress(), AudioManager.AUDIO_DEVICE_CATEGORY_OTHER);
+
+ verify(mMockAudioService,
+ timeout(MAX_MESSAGE_HANDLING_DELAY_MS).atLeast(1)).onUpdatedAdiDeviceState(
+ ArgumentMatchers.argThat(devState -> devState.getAudioDeviceCategory()
+ == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER));
}
private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception {
+ assertNotNull("invalid null BT device", mFakeBtDevice);
when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
.thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
when(mMockAudioService.isInCommunication()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
new file mode 100644
index 000000000000..330dbb83e949
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@Presubmit
+public class AnrTimerTest {
+
+ // The commonly used message timeout key.
+ private static final int MSG_TIMEOUT = 1;
+
+ // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
+ // different message instances. Additional fields (like what) capture delivery information
+ // that is checked by the test.
+ private static class TestArg {
+ final int pid;
+ final int uid;
+ int what;
+
+ TestArg(int pid, int uid) {
+ this.pid = pid;
+ this.uid = uid;
+ this.what = 0;
+ }
+ }
+
+ /**
+ * The test handler is a self-contained object for a single test.
+ */
+ private static class Helper {
+ final Object mLock = new Object();
+
+ final Handler mHandler;
+ final CountDownLatch mLatch;
+ @GuardedBy("mLock")
+ final ArrayList<TestArg> mMessages;
+
+ Helper(int expect) {
+ mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
+ mMessages = new ArrayList<>();
+ mLatch = new CountDownLatch(expect);
+ }
+
+ /**
+ * When a timer expires, the object must be a TestArg. Update the TestArg with
+ * expiration metadata and save it.
+ */
+ private boolean expirationHandler(Message msg) {
+ synchronized (mLock) {
+ TestArg arg = (TestArg) msg.obj;
+ arg.what = msg.what;
+ mMessages.add(arg);
+ mLatch.countDown();
+ return false;
+ }
+ }
+
+ boolean await(long timeout) throws InterruptedException {
+ // No need to synchronize, as the CountDownLatch is already thread-safe.
+ return mLatch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Fetch the received messages. Fail if the count of received messages is other than the
+ * expected count.
+ */
+ TestArg[] messages(int expected) {
+ synchronized (mLock) {
+ assertEquals(expected, mMessages.size());
+ return mMessages.toArray(new TestArg[expected]);
+ }
+ }
+ }
+
+ /**
+ * An instrumented AnrTimer.
+ */
+ private static class TestAnrTimer extends AnrTimer<TestArg> {
+ private TestAnrTimer(Handler h, int key, String tag) {
+ super(h, key, tag);
+ }
+
+ TestAnrTimer(Helper helper) {
+ this(helper.mHandler, MSG_TIMEOUT, caller());
+ }
+
+ void start(TestArg arg, long millis) {
+ start(arg, arg.pid, arg.uid, millis);
+ }
+
+ // Return the name of method that called the constructor, assuming that this function is
+ // called from inside the constructor. The calling method is used to name the AnrTimer
+ // instance so that logs are easier to understand.
+ private static String caller() {
+ final int n = 4;
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ if (stack.length < n+1) return "test";
+ return stack[n].getMethodName();
+ }
+ }
+
+ void validate(TestArg expected, TestArg actual) {
+ assertEquals(expected, actual);
+ assertEquals(actual.what, MSG_TIMEOUT);
+ }
+
+
+ /**
+ * Verify that a simple expiration succeeds. The timer is started for 10ms. The test
+ * procedure waits 5s for the expiration message, but under correct operation, the test will
+ * only take 10ms
+ */
+ @Test
+ public void testSimpleTimeout() throws Exception {
+ Helper helper = new Helper(1);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t = new TestArg(1, 1);
+ timer.start(t, 10);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(1);
+ validate(t, result[0]);
+ }
+
+ /**
+ * Verify that if three timers are scheduled, they are delivered in time order.
+ */
+ @Test
+ public void testMultipleTimers() throws Exception {
+ // Expect three messages.
+ Helper helper = new Helper(3);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t1 = new TestArg(1, 1);
+ TestArg t2 = new TestArg(1, 2);
+ TestArg t3 = new TestArg(1, 3);
+ timer.start(t1, 50);
+ timer.start(t2, 60);
+ timer.start(t3, 40);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(3);
+ validate(t3, result[0]);
+ validate(t1, result[1]);
+ validate(t2, result[2]);
+ }
+
+ /**
+ * Verify that a canceled timer is not delivered.
+ */
+ @Test
+ public void testCancelTimer() throws Exception {
+ // Expect two messages.
+ Helper helper = new Helper(2);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t1 = new TestArg(1, 1);
+ TestArg t2 = new TestArg(1, 2);
+ TestArg t3 = new TestArg(1, 3);
+ timer.start(t1, 50);
+ timer.start(t2, 60);
+ timer.start(t3, 40);
+ // Briefly pause.
+ assertFalse(helper.await(10));
+ timer.cancel(t1);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(2);
+ validate(t3, result[0]);
+ validate(t2, result[1]);
+ }
+}
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index 9eeec7c8ddda..2cdf54248ebc 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -29,6 +29,8 @@ android_test {
manifest: "AndroidManifest.xml",
package_name: "com.android.server.wm.flicker",
instrumentation_target_package: "com.android.server.wm.flicker",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
index f867ffb679c5..6f8f008cf85b 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.server.wm.flick">
+ package="com.android.server.wm.flicker">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
<!-- Read and write traces from external storage -->
diff --git a/tests/FlickerTests/ActivityEmbedding/trace_config/trace_config.textproto b/tests/FlickerTests/ActivityEmbedding/trace_config/trace_config.textproto
index c9a35aca9085..c4edc1a5c0f7 100644
--- a/tests/FlickerTests/ActivityEmbedding/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/ActivityEmbedding/trace_config/trace_config.textproto
@@ -62,12 +62,6 @@ data_sources: {
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 514f89511fb1..1d71f95ef64f 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -23,13 +23,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "FlickerServiceTests-src",
- srcs: [
- "src/**/*",
- ],
-}
-
java_defaults {
name: "FlickerTestsDefault",
platform_apis: true,
@@ -49,10 +42,7 @@ java_defaults {
"wm-flicker-common-app-helpers",
"wm-shell-flicker-utils",
],
- data: [
- ":FlickerTestApp",
- "trace_config/*",
- ],
+ data: [":FlickerTestApp"],
}
java_library {
diff --git a/tests/FlickerTests/AppClose/Android.bp b/tests/FlickerTests/AppClose/Android.bp
index 151d12f2a8ca..93fdd652510d 100644
--- a/tests/FlickerTests/AppClose/Android.bp
+++ b/tests/FlickerTests/AppClose/Android.bp
@@ -30,4 +30,5 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/AppClose/trace_config/trace_config.textproto b/tests/FlickerTests/AppClose/trace_config/trace_config.textproto
index c9a35aca9085..6a0afc60bc95 100644
--- a/tests/FlickerTests/AppClose/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/AppClose/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp
index f33384df6e7f..f5e962124b37 100644
--- a/tests/FlickerTests/AppLaunch/Android.bp
+++ b/tests/FlickerTests/AppLaunch/Android.bp
@@ -50,6 +50,7 @@ android_test {
"FlickerTestsBase",
"FlickerTestsAppLaunchCommon",
],
+ data: ["trace_config/*"],
}
android_test {
@@ -66,4 +67,5 @@ android_test {
"FlickerTestsBase",
"FlickerTestsAppLaunchCommon",
],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/AppLaunch/trace_config/trace_config.textproto b/tests/FlickerTests/AppLaunch/trace_config/trace_config.textproto
index c9a35aca9085..f27177ffee3e 100644
--- a/tests/FlickerTests/AppLaunch/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/AppLaunch/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/FlickerService/Android.bp b/tests/FlickerTests/FlickerService/Android.bp
index 1a381150dfb0..ef74e942bdba 100644
--- a/tests/FlickerTests/FlickerService/Android.bp
+++ b/tests/FlickerTests/FlickerService/Android.bp
@@ -30,4 +30,5 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/FlickerService/trace_config/trace_config.textproto b/tests/FlickerTests/FlickerService/trace_config/trace_config.textproto
index c9a35aca9085..a4f3ecfa8976 100644
--- a/tests/FlickerTests/FlickerService/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/FlickerService/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
+ atrace_apps: "com.android.server.wm.flicker.service"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index 057d9fcdb796..1141e5f3ae2f 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -40,6 +40,7 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
java_library {
@@ -59,6 +60,7 @@ android_test {
"FlickerTestsBase",
"FlickerTestsImeCommon",
],
+ data: ["trace_config/*"],
}
android_test {
@@ -75,4 +77,5 @@ android_test {
"FlickerTestsBase",
"FlickerTestsImeCommon",
],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/IME/trace_config/trace_config.textproto b/tests/FlickerTests/IME/trace_config/trace_config.textproto
index c9a35aca9085..b722fe5bc00a 100644
--- a/tests/FlickerTests/IME/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/IME/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/Notification/Android.bp b/tests/FlickerTests/Notification/Android.bp
index 5bed568aacd1..4648383b2771 100644
--- a/tests/FlickerTests/Notification/Android.bp
+++ b/tests/FlickerTests/Notification/Android.bp
@@ -30,4 +30,5 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/Notification/trace_config/trace_config.textproto b/tests/FlickerTests/Notification/trace_config/trace_config.textproto
index c9a35aca9085..dc8c88c5b41c 100644
--- a/tests/FlickerTests/Notification/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/Notification/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
+ atrace_apps: "com.android.server.wm.flicker.notification"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/QuickSwitch/Android.bp b/tests/FlickerTests/QuickSwitch/Android.bp
index 64f718333a59..8755d0e3b304 100644
--- a/tests/FlickerTests/QuickSwitch/Android.bp
+++ b/tests/FlickerTests/QuickSwitch/Android.bp
@@ -30,4 +30,5 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/QuickSwitch/trace_config/trace_config.textproto b/tests/FlickerTests/QuickSwitch/trace_config/trace_config.textproto
index c9a35aca9085..cd70ad59f1ed 100644
--- a/tests/FlickerTests/QuickSwitch/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/QuickSwitch/trace_config/trace_config.textproto
@@ -61,13 +61,7 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
atrace_apps: "com.android.server.wm.flicker.quickswitch"
- atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
atrace_apps: "com.google.android.apps.nexuslauncher"
diff --git a/tests/FlickerTests/Rotation/Android.bp b/tests/FlickerTests/Rotation/Android.bp
index 8e93b5b340c4..233a27691e21 100644
--- a/tests/FlickerTests/Rotation/Android.bp
+++ b/tests/FlickerTests/Rotation/Android.bp
@@ -30,4 +30,5 @@ android_test {
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/Rotation/trace_config/trace_config.textproto b/tests/FlickerTests/Rotation/trace_config/trace_config.textproto
index c9a35aca9085..eeb542f2a349 100644
--- a/tests/FlickerTests/Rotation/trace_config/trace_config.textproto
+++ b/tests/FlickerTests/Rotation/trace_config/trace_config.textproto
@@ -61,12 +61,6 @@ data_sources: {
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker"
- atrace_apps: "com.android.server.wm.flicker.other"
- atrace_apps: "com.android.server.wm.flicker.close"
- atrace_apps: "com.android.server.wm.flicker.ime"
- atrace_apps: "com.android.server.wm.flicker.launch"
- atrace_apps: "com.android.server.wm.flicker.quickswitch"
atrace_apps: "com.android.server.wm.flicker.rotation"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index ad272a052220..ce92eac3fc59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,10 +40,9 @@ abstract class BaseTest
constructor(
protected val flicker: LegacyFlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
- protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
- init {
- tapl.setExpectedRotationCheckEnabled(true)
+ protected val tapl: LauncherInstrumentation by lazy {
+ LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
}
private val logTag = this::class.java.simpleName