From 87e81c22fa3531b378ef4bce06026c0f602ff5a1 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Fri, 4 Aug 2023 13:19:00 -0700 Subject: [PATCH 01/95] Update simple_list_item_single_choice to work better with large fonts. The list is used by newChooseAccountIntent. Test: manual - b/279879914#comment8 Bug: 279879914 Change-Id: I499d73f14301ac873218cd57ef032d64f572886d --- core/res/res/layout/simple_list_item_multiple_choice.xml | 3 ++- core/res/res/layout/simple_list_item_single_choice.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/res/res/layout/simple_list_item_multiple_choice.xml b/core/res/res/layout/simple_list_item_multiple_choice.xml index 440b6fd8e1b1..fd9bf76aade5 100644 --- a/core/res/res/layout/simple_list_item_multiple_choice.xml +++ b/core/res/res/layout/simple_list_item_multiple_choice.xml @@ -17,7 +17,8 @@ Date: Fri, 8 Sep 2023 10:05:03 +0000 Subject: [PATCH 02/95] Add a hidden API to query quarantine state. Bug: 297934650 Test: manual Change-Id: Ic1f295e61cdbc2c51d04c998c08d7bd4c7cb9aae --- core/java/android/app/ApplicationPackageManager.java | 11 +++++++++++ core/java/android/content/pm/PackageManager.java | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index fcd13b840cb1..7b128f53857f 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2945,6 +2945,17 @@ public class ApplicationPackageManager extends PackageManager { return isPackageSuspendedForUser(mContext.getOpPackageName(), getUserId()); } + @Override + public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException { + try { + return mPM.isPackageQuarantinedForUser(packageName, getUserId()); + } catch (IllegalArgumentException ie) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ @Override public void setApplicationCategoryHint(String packageName, int categoryHint) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9a53a2a60076..2980134f3fb4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -9859,6 +9859,18 @@ public abstract class PackageManager { throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); } + /** + * Query if an app is currently quarantined. + * + * @return {@code true} if the given package is quarantined, {@code false} otherwise + * @throws NameNotFoundException if the package could not be found. + * + * @hide + */ + public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException { + throw new UnsupportedOperationException("isPackageQuarantined not implemented"); + } + /** * Provide a hint of what the {@link ApplicationInfo#category} value should * be for the given package. -- GitLab From 38dc14e8e0dc1e2fb96e7220502cbeb40fdd7f92 Mon Sep 17 00:00:00 2001 From: Prashant Patil Date: Wed, 16 Aug 2023 15:00:24 +0000 Subject: [PATCH 03/95] Fix attestation properties reading logic Fixed the logic of reading attestation specific properties in Build.java. Bug: 110779648 Test: atest CtsKeystoreTestCases:android.keystore.cts.KeyAttestationTest CtsKeystoreTestCases:DeviceOwnerKeyManagementTest (cherry picked from https://android-review.googlesource.com/q/commit:316e3d16c9f34212f3beace7695289651d15a071) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4edeaffcbb9d39efaf49d3e24a621110c367c560) Merged-In: I9af7daa08cbd8bb52f3509fe93f787ba94b7bbc3 Change-Id: I9af7daa08cbd8bb52f3509fe93f787ba94b7bbc3 --- core/java/android/os/Build.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 9f9c2222f9d9..cd6acfe149f8 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1555,7 +1555,7 @@ public class Build { String attestProp = getString( TextUtils.formatSimple("ro.product.%s_for_attestation", property)); return attestProp.equals(UNKNOWN) - ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN; + ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : attestProp; } private static String[] getStringList(String property, String separator) { -- GitLab From 4aed12b94ca786dfc2e6bbeb5e8b527209654207 Mon Sep 17 00:00:00 2001 From: Haoran Zhang Date: Thu, 7 Sep 2023 22:42:01 +0000 Subject: [PATCH 04/95] [Webview-FillDialog Support] Fix autofill framework issue found when testing webView prorotype There are two issues I found: 1. Instead of SparseArray.indexOf(i), framework should use SparseArray.keyOf(i) to get the virtual id for virtual views that Chrome passed in. 2. Framework should ignore case when checking whether view's autofill hints is in fill dialog supported hints. In the webview prototype, Chrome is passing "PASSWORD" as autofill hints Also I found it's easy to create null pointer exception when referencing AutofillHints in notifyViewReadyInner(). It's not obvious that this array could be null also. Annotated it with @Nullable to make it obvious. Bug:b/299532529 Test: atest CtsAutoFillServiceTestCases Change-Id: Ie1ed6f0fbe806826a1844d85a74767ca946e55b8 --- .../view/autofill/AutofillManager.java | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index d729d494e104..a7fbaf63eaed 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1436,7 +1436,7 @@ public final class AutofillManager { } for (int i = 0; i < infos.size(); i++) { final VirtualViewFillInfo info = infos.valueAt(i); - final int virtualId = infos.indexOfKey(i); + final int virtualId = infos.keyAt(i); notifyViewReadyInner(getAutofillId(view, virtualId), (info == null) ? null : info.getAutofillHints()); } @@ -1450,9 +1450,6 @@ public final class AutofillManager { * @hide */ public void notifyViewEnteredForFillDialog(View v) { - if (sDebug) { - Log.d(TAG, "notifyViewEnteredForFillDialog:" + v.getAutofillId()); - } if (v.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) { if (sDebug) { @@ -1465,11 +1462,14 @@ public final class AutofillManager { notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints()); } - private void notifyViewReadyInner(AutofillId id, String[] autofillHints) { + private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints) { + if (sDebug) { + Log.d(TAG, "notifyViewReadyInner:" + id); + } + if (!hasAutofillFeature()) { return; } - synchronized (mLock) { if (mAllTrackedViews.contains(id)) { // The id is tracked and will not trigger pre-fill request again. @@ -1505,26 +1505,38 @@ public final class AutofillManager { final boolean clientAdded = tryAddServiceClientIfNeededLocked(); if (clientAdded) { startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null, - /* value= */ null, /* flags= */ FLAG_PCC_DETECTION); + /* value= */ null, /* flags= */ FLAG_PCC_DETECTION); } else { if (sVerbose) { Log.v(TAG, "not starting session: no service client"); } } - } } } - if (mIsFillDialogEnabled - || ArrayUtils.containsAny(autofillHints, mFillDialogEnabledHints)) { + // Check if framework should send pre-fill request for fill dialog + boolean shouldSendPreFillRequestForFillDialog = false; + if (mIsFillDialogEnabled) { + shouldSendPreFillRequestForFillDialog = true; + } else if (autofillHints != null) { + // check if supported autofill hint is present + for (String autofillHint : autofillHints) { + for (String filldialogEnabledHint : mFillDialogEnabledHints) { + if (filldialogEnabledHint.equalsIgnoreCase(autofillHint)) { + shouldSendPreFillRequestForFillDialog = true; + break; + } + } + if (shouldSendPreFillRequestForFillDialog) break; + } + } + if (shouldSendPreFillRequestForFillDialog) { if (sDebug) { Log.d(TAG, "Triggering pre-emptive request for fill dialog."); } - int flags = FLAG_SUPPORTS_FILL_DIALOG; flags |= FLAG_VIEW_NOT_FOCUSED; - synchronized (mLock) { // To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill // request, because IME will reset the id of IME served view to 0 when activity @@ -1532,9 +1544,10 @@ public final class AutofillManager { // not match the IME served view's, Autofill will be blocking to wait inline // request from the IME. notifyViewEnteredLocked(/* view= */ null, AutofillId.NO_AUTOFILL_ID, - /* bounds= */ null, /* value= */ null, flags); + /* bounds= */ null, /* value= */ null, flags); } } + return; } private boolean hasFillDialogUiFeature() { -- GitLab From 114d7f81e9136b9353a4e739cc19d02dabad229c Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 11 Sep 2023 19:06:31 +0000 Subject: [PATCH 05/95] Remove delay_bouncer flag Test: builds Fixes: 279794160 Change-Id: I88312a4c690a46fee56ebb8fb3d3872ed1746c05 --- .../domain/interactor/PrimaryBouncerInteractor.kt | 15 ++++----------- .../src/com/android/systemui/flags/Flags.kt | 4 ---- ...guardViewLegacyControllerWithCoroutinesTest.kt | 3 --- .../interactor/PrimaryBouncerInteractorTest.kt | 5 ----- .../PrimaryBouncerInteractorWithCoroutinesTest.kt | 3 --- .../ui/viewmodel/KeyguardBouncerViewModelTest.kt | 3 --- .../interactor/KeyguardFaceAuthInteractorTest.kt | 1 - .../OccludingAppDeviceEntryInteractorTest.kt | 7 +------ .../KeyguardDismissInteractorFactory.kt | 2 -- 9 files changed, 5 insertions(+), 38 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 579f0b7b3185..3fd6617d5a81 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -38,8 +38,6 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.plugins.ActivityStarter @@ -74,7 +72,6 @@ constructor( private val context: Context, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val trustRepository: TrustRepository, - private val featureFlags: FeatureFlags, @Application private val applicationScope: CoroutineScope, ) { private val passiveAuthBouncerDelay = @@ -135,11 +132,9 @@ constructor( init { keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) { - applicationScope.launch { - trustRepository.isCurrentUserActiveUnlockRunning.collect { - currentUserActiveUnlockRunning = it - } + applicationScope.launch { + trustRepository.isCurrentUserActiveUnlockRunning.collect { + currentUserActiveUnlockRunning = it } } } @@ -415,9 +410,7 @@ constructor( currentUserActiveUnlockRunning && keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState() - return featureFlags.isEnabled(Flags.DELAY_BOUNCER) && - !needsFullscreenBouncer() && - (canRunFaceAuth || canRunActiveUnlock) + return !needsFullscreenBouncer() && (canRunFaceAuth || canRunActiveUnlock) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7e8f68225070..7527587c4067 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -253,10 +253,6 @@ object Flags { // TODO(b/277961132): Tracking bug. @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages") - /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */ - // TODO(b/279794160): Tracking bug. - @JvmField val DELAY_BOUNCER = releasedFlag("delay_bouncer") - /** Keyguard Migration */ /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 1885f6499299..17f435b75d5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -30,8 +30,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository @@ -99,7 +97,6 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : context, mKeyguardUpdateMonitor, FakeTrustRepository(), - FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }, testScope.backgroundScope, ) mAlternateBouncerInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 420fdba7fde2..a68e0192e951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -34,8 +34,6 @@ import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.bouncer.ui.BouncerViewDelegate import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.plugins.ActivityStarter @@ -76,7 +74,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { private lateinit var underTest: PrimaryBouncerInteractor private lateinit var resources: TestableResources private lateinit var trustRepository: FakeTrustRepository - private lateinit var featureFlags: FakeFeatureFlags private lateinit var testScope: TestScope @Before @@ -89,7 +86,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { testScope = TestScope() mainHandler = FakeHandler(android.os.Looper.getMainLooper()) trustRepository = FakeTrustRepository() - featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) } underTest = PrimaryBouncerInteractor( repository, @@ -103,7 +99,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { context, keyguardUpdateMonitor, trustRepository, - featureFlags, testScope.backgroundScope, ) whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index 665456d5cd80..cb0b74f3015d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -27,8 +27,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -77,7 +75,6 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { context, keyguardUpdateMonitor, Mockito.mock(TrustRepository::class.java), - FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }, TestScope().backgroundScope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index d4bba723a989..333bd214fe9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -31,8 +31,6 @@ import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -85,7 +83,6 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { context, keyguardUpdateMonitor, Mockito.mock(TrustRepository::class.java), - FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }, TestScope().backgroundScope, ) underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index da70a9ff036f..4d3ed75de8a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -126,7 +126,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { context, keyguardUpdateMonitor, FakeTrustRepository(), - FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }, testScope.backgroundScope, ), AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt index b81a3d46b434..47365457d9e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt @@ -100,11 +100,7 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() configurationRepository = FakeConfigurationRepository() - featureFlags = - FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, false) - set(Flags.DELAY_BOUNCER, false) - } + featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } trustRepository = FakeTrustRepository() powerRepository = FakePowerRepository() underTest = @@ -138,7 +134,6 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { context, keyguardUpdateMonitor, trustRepository, - featureFlags, testScope.backgroundScope, ), AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index 6dd41f488e2d..ceab8e9e52ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -72,7 +72,6 @@ object KeyguardDismissInteractorFactory { keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java), featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic().apply { - set(Flags.DELAY_BOUNCER, true) set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true) set(Flags.FULL_SCREEN_USER_SWITCHER, false) }, @@ -92,7 +91,6 @@ object KeyguardDismissInteractorFactory { context, keyguardUpdateMonitor, trustRepository, - featureFlags, testScope.backgroundScope, ) val alternateBouncerInteractor = -- GitLab From 8d2fb5b77bf8018833c3e9b70464b173d5c9099b Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 4 Sep 2023 14:43:52 +0800 Subject: [PATCH 06/95] Synchronize window config updates (5/n) ActivityToken is included in individual transaction item. Cleanup the activityToken in ClientTransaction. Bug: 260873529 Test: atest FrameworksCoreTests:ObjectPoolTests Test: atest FrameworksCoreTests:TransactionExecutorTests Change-Id: Ib6e953728b9886eee3d1223befd46b58a3cedbf5 --- core/java/android/app/ActivityThread.java | 12 +-- .../ActivityConfigurationChangeItem.java | 5 +- .../ActivityRelaunchItem.java | 4 +- .../ActivityTransactionItem.java | 15 +++- .../servertransaction/BaseClientRequest.java | 21 +++-- .../servertransaction/ClientTransaction.java | 76 +++++++++--------- .../ClientTransactionItem.java | 11 ++- .../ConfigurationChangeItem.java | 8 +- .../DestroyActivityItem.java | 2 +- .../servertransaction/LaunchActivityItem.java | 6 +- .../servertransaction/MoveToDisplayItem.java | 2 +- .../servertransaction/PauseActivityItem.java | 2 +- .../RefreshCallbackItem.java | 2 +- .../servertransaction/ResumeActivityItem.java | 4 +- .../servertransaction/StopActivityItem.java | 2 +- .../TopResumedActivityChangeItem.java | 2 +- .../TransactionExecutor.java | 24 +++--- .../TransactionExecutorHelper.java | 9 +-- .../WindowContextInfoChangeItem.java | 5 +- .../WindowContextWindowRemovalItem.java | 2 +- .../app/activity/ActivityThreadTest.java | 40 ++++++---- .../ActivityConfigurationChangeItemTest.java | 2 +- .../ClientTransactionTests.java | 20 +++-- .../ConfigurationChangeItemTest.java | 5 +- .../servertransaction/ObjectPoolTests.java | 20 ++++- .../TransactionExecutorTests.java | 36 ++++----- .../TransactionParcelTests.java | 12 +-- .../WindowContextInfoChangeItemTest.java | 6 +- .../WindowContextWindowRemovalItemTest.java | 4 +- .../server/wm/ActivityClientController.java | 6 +- .../com/android/server/wm/ActivityRecord.java | 28 +++---- .../server/wm/ActivityTaskSupervisor.java | 2 +- .../server/wm/ClientLifecycleManager.java | 80 +++---------------- .../wm/DisplayRotationCompatPolicy.java | 2 +- .../com/android/server/wm/TaskFragment.java | 6 +- .../server/wm/ActivityRecordTests.java | 11 +-- .../wm/ClientLifecycleManagerTests.java | 7 +- .../wm/DisplayRotationCompatPolicyTests.java | 3 +- .../WindowContextListenerControllerTests.java | 2 +- .../wm/WindowProcessControllerTests.java | 2 +- 40 files changed, 231 insertions(+), 277 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 00e546ad25b0..77c239a60844 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -36,6 +36,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; @@ -3663,10 +3664,9 @@ public final class ActivityThread extends ClientTransactionHandler int resultCode, Intent data) { if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id + " req=" + requestCode + " res=" + resultCode + " data=" + data); - ArrayList list = new ArrayList(); + final ArrayList list = new ArrayList<>(); list.add(new ResultInfo(id, requestCode, resultCode, data)); - final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, - activityToken); + final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread); clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list)); try { mAppThread.scheduleTransaction(clientTransaction); @@ -4429,7 +4429,7 @@ public final class ActivityThread extends ClientTransactionHandler } private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { - final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); + final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token, r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false, /* autoEnteringPip */ false)); @@ -4437,7 +4437,7 @@ public final class ActivityThread extends ClientTransactionHandler } private void scheduleResume(ActivityClientRecord r) { - final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); + final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false)); executeTransaction(transaction); @@ -6029,7 +6029,7 @@ public final class ActivityThread extends ClientTransactionHandler final ActivityLifecycleItem lifecycleRequest = TransactionExecutorHelper.getLifecycleRequestForCurrentState(r); // Schedule the transaction. - final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); + final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); transaction.addCallback(activityRelaunchItem); transaction.setLifecycleStateRequest(lifecycleRequest); executeTransaction(transaction); diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index c2c54278a84e..dd332c850d5d 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -41,7 +41,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { private Configuration mConfiguration; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @Nullable IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration); // Notify the client of an upcoming change in the token configuration. This ensures that // batches of config change items only process the newest configuration. @@ -59,8 +59,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @Nullable @Override - public Context getContextToUpdate(@NonNull ClientTransactionHandler client, - @Nullable IBinder token) { + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { return client.getActivity(getActivityToken()); } diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java index 491d0260f6fb..a5dd115d78b3 100644 --- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java +++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java @@ -56,7 +56,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { private ActivityClientRecord mActivityClientRecord; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { // The local config is already scaled so only apply if this item is from server side. if (!client.isExecutingLocalTransaction()) { CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig); @@ -78,7 +78,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { final ActivityClientRecord r = getActivityClientRecord(client); client.reportRelaunch(r); diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index 0f8879e1429b..2a65b3528145 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -16,6 +16,8 @@ package android.app.servertransaction; +import static android.app.servertransaction.TransactionExecutorHelper.getActivityName; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.CallSuper; @@ -28,6 +30,7 @@ import android.os.Parcel; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; import java.util.Objects; /** @@ -49,14 +52,14 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { ActivityTransactionItem() {} @Override - public final void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public final void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { final ActivityClientRecord r = getActivityClientRecord(client); execute(client, r, pendingActions); } /** - * Like {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)}, + * Like {@link #execute(ClientTransactionHandler, PendingTransactionActions)}, * but take non-null {@link ActivityClientRecord} as a parameter. */ @VisibleForTesting(visibility = PACKAGE) @@ -111,6 +114,14 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { mActivityToken = null; } + @Override + void dump(@NonNull String prefix, @NonNull PrintWriter pw, + @NonNull ClientTransactionHandler transactionHandler) { + super.dump(prefix, pw, transactionHandler); + pw.append(prefix).append("Target activity: ") + .println(getActivityName(mActivityToken, transactionHandler)); + } + // Subclass must override and call super.equals to compare the mActivityToken. @SuppressWarnings("EqualsGetClass") @CallSuper diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java index c91e0ca5ffc1..f2751752abd8 100644 --- a/core/java/android/app/servertransaction/BaseClientRequest.java +++ b/core/java/android/app/servertransaction/BaseClientRequest.java @@ -16,8 +16,8 @@ package android.app.servertransaction; +import android.annotation.NonNull; import android.app.ClientTransactionHandler; -import android.os.IBinder; /** * Base interface for individual requests from server to client. @@ -27,31 +27,28 @@ import android.os.IBinder; public interface BaseClientRequest extends ObjectPoolItem { /** - * Prepare the client request before scheduling. + * Prepares the client request before scheduling. * An example of this might be informing about pending updates for some values. * * @param client Target client handler. - * @param token Target activity token. */ - default void preExecute(ClientTransactionHandler client, IBinder token) { + default void preExecute(@NonNull ClientTransactionHandler client) { } /** - * Execute the request. + * Executes the request. * @param client Target client handler. - * @param token Target activity token. * @param pendingActions Container that may have data pending to be used. */ - void execute(ClientTransactionHandler client, IBinder token, - PendingTransactionActions pendingActions); + void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions); /** - * Perform all actions that need to happen after execution, e.g. report the result to server. + * Performs all actions that need to happen after execution, e.g. report the result to server. * @param client Target client handler. - * @param token Target activity token. * @param pendingActions Container that may have data pending to be used. */ - default void postExecute(ClientTransactionHandler client, IBinder token, - PendingTransactionActions pendingActions) { + default void postExecute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { } } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index ee14708c38a8..a5b0f18dad3b 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -16,6 +16,9 @@ package android.app.servertransaction; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; @@ -36,7 +39,7 @@ import java.util.Objects; * A container that holds a sequence of messages, which may be sent to a client. * This includes a list of callbacks and a final lifecycle state. * - * @see com.android.server.am.ClientLifecycleManager + * @see com.android.server.wm.ClientLifecycleManager * @see ClientTransactionItem * @see ActivityLifecycleItem * @hide @@ -56,9 +59,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Target client. */ private IApplicationThread mClient; - /** Target client activity. Might be null if the entire transaction is targeting an app. */ - private IBinder mActivityToken; - /** Get the target client of the transaction. */ public IApplicationThread getClient() { return mClient; @@ -68,7 +68,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { * Add a message to the end of the sequence of callbacks. * @param activityCallback A single message that can contain a lifecycle request/callback. */ - public void addCallback(ClientTransactionItem activityCallback) { + public void addCallback(@NonNull ClientTransactionItem activityCallback) { if (mActivityCallbacks == null) { mActivityCallbacks = new ArrayList<>(); } @@ -87,11 +87,21 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Nullable @UnsupportedAppUsage public IBinder getActivityToken() { - return mActivityToken; + // TODO(b/260873529): remove after we allow multiple activity items in one transaction. + if (mLifecycleStateRequest != null) { + return mLifecycleStateRequest.getActivityToken(); + } + for (int i = mActivityCallbacks.size() - 1; i >= 0; i--) { + final IBinder token = mActivityCallbacks.get(i).getActivityToken(); + if (token != null) { + return token; + } + } + return null; } /** Get the target state lifecycle request. */ - @VisibleForTesting + @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; @@ -101,7 +111,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { * Set the lifecycle state in which the client should be after executing the transaction. * @param stateRequest A lifecycle request initialized with right parameters. */ - public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) { + public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) { mLifecycleStateRequest = stateRequest; } @@ -110,15 +120,15 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { * @param clientTransactionHandler Handler on the client side that will executed all operations * requested by transaction items. */ - public void preExecute(android.app.ClientTransactionHandler clientTransactionHandler) { + public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) { if (mActivityCallbacks != null) { final int size = mActivityCallbacks.size(); for (int i = 0; i < size; ++i) { - mActivityCallbacks.get(i).preExecute(clientTransactionHandler, mActivityToken); + mActivityCallbacks.get(i).preExecute(clientTransactionHandler); } } if (mLifecycleStateRequest != null) { - mLifecycleStateRequest.preExecute(clientTransactionHandler, mActivityToken); + mLifecycleStateRequest.preExecute(clientTransactionHandler); } } @@ -141,14 +151,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { private ClientTransaction() {} - /** Obtain an instance initialized with provided params. */ - public static ClientTransaction obtain(IApplicationThread client, IBinder activityToken) { + /** Obtains an instance initialized with provided params. */ + @NonNull + public static ClientTransaction obtain(@Nullable IApplicationThread client) { ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class); if (instance == null) { instance = new ClientTransaction(); } instance.mClient = client; - instance.mActivityToken = activityToken; return instance; } @@ -167,7 +177,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { mLifecycleStateRequest = null; } mClient = null; - mActivityToken = null; ObjectPool.recycle(this); } @@ -175,12 +184,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Write to Parcel. */ @Override - public void writeToParcel(Parcel dest, int flags) { - final boolean writeActivityToken = mActivityToken != null; - dest.writeBoolean(writeActivityToken); - if (writeActivityToken) { - dest.writeStrongBinder(mActivityToken); - } + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mLifecycleStateRequest, flags); final boolean writeActivityCallbacks = mActivityCallbacks != null; dest.writeBoolean(writeActivityCallbacks); @@ -190,11 +194,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { } /** Read from Parcel. */ - private ClientTransaction(Parcel in) { - final boolean readActivityToken = in.readBoolean(); - if (readActivityToken) { - mActivityToken = in.readStrongBinder(); - } + private ClientTransaction(@NonNull Parcel in) { mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class); final boolean readActivityCallbacks = in.readBoolean(); if (readActivityCallbacks) { @@ -203,9 +203,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { } } - public static final @android.annotation.NonNull Creator CREATOR = - new Creator() { - public ClientTransaction createFromParcel(Parcel in) { + public static final @NonNull Creator CREATOR = new Creator<>() { + public ClientTransaction createFromParcel(@NonNull Parcel in) { return new ClientTransaction(in); } @@ -230,8 +229,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { final ClientTransaction other = (ClientTransaction) o; return Objects.equals(mActivityCallbacks, other.mActivityCallbacks) && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) - && mClient == other.mClient - && mActivityToken == other.mActivityToken; + && mClient == other.mClient; } @Override @@ -240,26 +238,32 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { result = 31 * result + Objects.hashCode(mActivityCallbacks); result = 31 * result + Objects.hashCode(mLifecycleStateRequest); result = 31 * result + Objects.hashCode(mClient); - result = 31 * result + Objects.hashCode(mActivityToken); return result; } /** Dump transaction items callback items and final lifecycle state request. */ - public void dump(String prefix, PrintWriter pw) { + void dump(@NonNull String prefix, @NonNull PrintWriter pw, + @NonNull ClientTransactionHandler transactionHandler) { pw.append(prefix).println("ClientTransaction{"); pw.append(prefix).print(" callbacks=["); + final String itemPrefix = prefix + " "; final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0; if (size > 0) { pw.println(); for (int i = 0; i < size; i++) { - pw.append(prefix).append(" ").println(mActivityCallbacks.get(i).toString()); + mActivityCallbacks.get(i).dump(itemPrefix, pw, transactionHandler); } pw.append(prefix).println(" ]"); } else { pw.println("]"); } - pw.append(prefix).append(" stateRequest=").println(mLifecycleStateRequest != null - ? mLifecycleStateRequest.toString() : null); + + pw.append(prefix).println(" stateRequest="); + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.dump(itemPrefix, pw, transactionHandler); + } else { + pw.append(itemPrefix).println("null"); + } pw.append(prefix).println("}"); } } diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java index 30fc104a71b7..07e5a7dc5f02 100644 --- a/core/java/android/app/servertransaction/ClientTransactionItem.java +++ b/core/java/android/app/servertransaction/ClientTransactionItem.java @@ -30,6 +30,8 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; + /** * A callback message to a client that can be scheduled and executed. * Examples of these might be activity configuration change, multi-window mode change, activity @@ -56,8 +58,7 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel * it is updating; otherwise, returns {@code null}. */ @Nullable - public Context getContextToUpdate(@NonNull ClientTransactionHandler client, - @NonNull IBinder token) { + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { return null; } @@ -71,6 +72,12 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel return null; } + /** Dumps this transaction item. */ + void dump(@NonNull String prefix, @NonNull PrintWriter pw, + @NonNull ClientTransactionHandler transactionHandler) { + pw.append(prefix).println(this); + } + // Parcelable @Override diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java index f72e2e04deca..96961aced987 100644 --- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java @@ -23,7 +23,6 @@ import android.app.ClientTransactionHandler; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.os.IBinder; import android.os.Parcel; import java.util.Objects; @@ -38,21 +37,20 @@ public class ConfigurationChangeItem extends ClientTransactionItem { private int mDeviceId; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @Nullable IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration); client.updatePendingConfiguration(mConfiguration); } @Override - public void execute(@NonNull ClientTransactionHandler client, @Nullable IBinder token, + public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { client.handleConfigurationChanged(mConfiguration, mDeviceId); } @Nullable @Override - public Context getContextToUpdate(@NonNull ClientTransactionHandler client, - @Nullable IBinder token) { + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { return ActivityThread.currentApplication(); } diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index a327a99435ce..ddb6df10517c 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -36,7 +36,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { private int mConfigChanges; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { client.getActivitiesToBeDestroyed().put(getActivityToken(), this); } diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 9b37a35cdb8f..a64c744c70ba 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -84,7 +84,7 @@ public class LaunchActivityItem extends ClientTransactionItem { private IActivityClientController mActivityClientController; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { client.countLaunchingActivities(1); client.updateProcessState(mProcState, false); CompatibilityInfo.applyOverrideScaleIfNeeded(mCurConfig); @@ -96,7 +96,7 @@ public class LaunchActivityItem extends ClientTransactionItem { } @Override - public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo, @@ -109,7 +109,7 @@ public class LaunchActivityItem extends ClientTransactionItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { client.countLaunchingActivities(-1); } diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index fb57bed94160..e56d3f862b1b 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -40,7 +40,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { private Configuration mConfiguration; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration); // Notify the client of an upcoming change in the token configuration. This ensures that // batches of config change items only process the newest configuration. diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index a8e6772b4e32..8f1e90b985e6 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -56,7 +56,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { if (mDontReport) { return; diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java index 00128f0d298f..368ed765b922 100644 --- a/core/java/android/app/servertransaction/RefreshCallbackItem.java +++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java @@ -51,7 +51,7 @@ public class RefreshCallbackItem extends ActivityTransactionItem { @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) {} @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { final ActivityClientRecord r = getActivityClientRecord(client); client.reportRefresh(r); diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index b11e73cbef96..4a0ea98ccb89 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -44,7 +44,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { private boolean mShouldSendCompatFakeFocus; @Override - public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) { + public void preExecute(@NonNull ClientTransactionHandler client) { if (mUpdateProcState) { client.updateProcessState(mProcState, false); } @@ -60,7 +60,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { // TODO(lifecycler): Use interface callback instead of actual implementation. ActivityClient.getInstance().activityResumed(getActivityToken(), diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index f4325670c4fc..b8ce52da5a0c 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -46,7 +46,7 @@ public class StopActivityItem extends ActivityLifecycleItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { client.reportStop(pendingActions); } diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java index 693599fa229c..23d4505c1c9e 100644 --- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java +++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java @@ -43,7 +43,7 @@ public class TopResumedActivityChangeItem extends ActivityTransactionItem { } @Override - public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void postExecute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { if (mOnTop) { return; diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index d080162e4b8c..44336735254d 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -74,7 +74,7 @@ public class TransactionExecutor { * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will * either remain in the initial state, or last state needed by a callback. */ - public void execute(ClientTransaction transaction) { + public void execute(@NonNull ClientTransaction transaction) { if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction"); final IBinder token = transaction.getActivityToken(); @@ -109,7 +109,7 @@ public class TransactionExecutor { /** Cycle through all states requested by callbacks and execute them at proper times. */ @VisibleForTesting - public void executeCallbacks(ClientTransaction transaction) { + public void executeCallbacks(@NonNull ClientTransaction transaction) { final List callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.isEmpty()) { // No callbacks to execute, return early. @@ -117,9 +117,6 @@ public class TransactionExecutor { } if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callbacks in transaction"); - final IBinder token = transaction.getActivityToken(); - ActivityClientRecord r = mTransactionHandler.getActivityClient(token); - // In case when post-execution state of the last callback matches the final state requested // for the activity in this transaction, we won't do the last transition here and do it when // moving to final state instead (because it may contain additional parameters from server). @@ -135,6 +132,9 @@ public class TransactionExecutor { final int size = callbacks.size(); for (int i = 0; i < size; ++i) { final ClientTransactionItem item = callbacks.get(i); + final IBinder token = item.getActivityToken(); + ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item); final int postExecutionState = item.getPostExecutionState(); @@ -150,13 +150,13 @@ public class TransactionExecutor { final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated() && syncWindowConfigUpdateFlag(); final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled - ? item.getContextToUpdate(mTransactionHandler, token) + ? item.getContextToUpdate(mTransactionHandler) : null; final Configuration preExecutedConfig = configUpdatedContext != null ? new Configuration(configUpdatedContext.getResources().getConfiguration()) : null; - item.execute(mTransactionHandler, token, mPendingActions); + item.execute(mTransactionHandler, mPendingActions); if (configUpdatedContext != null) { final Configuration postExecutedConfig = configUpdatedContext.getResources() @@ -169,7 +169,7 @@ public class TransactionExecutor { } } - item.postExecute(mTransactionHandler, token, mPendingActions); + item.postExecute(mTransactionHandler, mPendingActions); if (r == null) { // Launch activity request will create an activity record. r = mTransactionHandler.getActivityClient(token); @@ -195,14 +195,14 @@ public class TransactionExecutor { } /** Transition to the final state if requested by the transaction. */ - private void executeLifecycleState(ClientTransaction transaction) { + private void executeLifecycleState(@NonNull ClientTransaction transaction) { final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest(); if (lifecycleItem == null) { // No lifecycle request, return early. return; } - final IBinder token = transaction.getActivityToken(); + final IBinder token = lifecycleItem.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); if (DEBUG_RESOLVER) { Slog.d(TAG, tId(transaction) + "Resolving lifecycle state: " @@ -219,8 +219,8 @@ public class TransactionExecutor { cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction); // Execute the final transition with proper parameters. - lifecycleItem.execute(mTransactionHandler, token, mPendingActions); - lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions); + lifecycleItem.execute(mTransactionHandler, mPendingActions); + lifecycleItem.postExecute(mTransactionHandler, mPendingActions); } /** Transition the client between states. */ diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index 0f9c517e2916..7e89a5b45a2d 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -26,6 +26,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; +import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; @@ -266,14 +267,12 @@ public class TransactionExecutorHelper { } /** Dump transaction to string. */ - static String transactionToString(ClientTransaction transaction, - ClientTransactionHandler transactionHandler) { + static String transactionToString(@NonNull ClientTransaction transaction, + @NonNull ClientTransactionHandler transactionHandler) { final StringWriter stringWriter = new StringWriter(); final PrintWriter pw = new PrintWriter(stringWriter); final String prefix = tId(transaction); - transaction.dump(prefix, pw); - pw.append(prefix + "Target activity: ") - .println(getActivityName(transaction.getActivityToken(), transactionHandler)); + transaction.dump(prefix, pw, transactionHandler); return stringWriter.toString(); } diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java index 99824b000b70..375d1bf57174 100644 --- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java +++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java @@ -41,15 +41,14 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem { private WindowContextInfo mInfo; @Override - public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { client.handleWindowContextInfoChanged(mClientToken, mInfo); } @Nullable @Override - public Context getContextToUpdate(@NonNull ClientTransactionHandler client, - @Nullable IBinder token) { + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { return client.getWindowContext(mClientToken); } diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java index ed52a6496e95..1bea4682928a 100644 --- a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java +++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java @@ -36,7 +36,7 @@ public class WindowContextWindowRemovalItem extends ClientTransactionItem { private IBinder mClientToken; @Override - public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { client.handleWindowContextWindowRemoval(mClientToken); } diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 89355072e29e..36e122301ba2 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -33,7 +33,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.annotation.Nullable; +import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityThread; import android.app.ActivityThread.ActivityClientRecord; @@ -226,7 +226,7 @@ public class ActivityThreadTest { CompatibilityInfo.setOverrideInvertedScale(scale); try { // Send process level config change. - ClientTransaction transaction = newTransaction(activityThread, null); + ClientTransaction transaction = newTransaction(activityThread); transaction.addCallback(ConfigurationChangeItem.obtain( new Configuration(newConfig), DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); @@ -243,7 +243,7 @@ public class ActivityThreadTest { // Send activity level config change. newConfig.seq++; newConfig.smallestScreenWidthDp++; - transaction = newTransaction(activityThread, activity.getActivityToken()); + transaction = newTransaction(activityThread); transaction.addCallback(ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), new Configuration(newConfig))); appThread.scheduleTransaction(transaction); @@ -444,12 +444,12 @@ public class ActivityThreadTest { activity.mConfigLatch = new CountDownLatch(1); activity.mTestLatch = new CountDownLatch(1); - ClientTransaction transaction = newTransaction(activityThread, null); + ClientTransaction transaction = newTransaction(activityThread); transaction.addCallback(ConfigurationChangeItem.obtain( processConfigLandscape, DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); - transaction = newTransaction(activityThread, activity.getActivityToken()); + transaction = newTransaction(activityThread); transaction.addCallback(ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), activityConfigLandscape)); transaction.addCallback(ConfigurationChangeItem.obtain( @@ -829,7 +829,8 @@ public class ActivityThreadTest { return thread.getActivityClient(token); } - private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { + @NonNull + private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) { final Configuration currentConfig = activity.getResources().getConfiguration(); final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain( activity.getActivityToken(), null, null, 0, @@ -846,7 +847,8 @@ public class ActivityThreadTest { return transaction; } - private static ClientTransaction newResumeTransaction(Activity activity) { + @NonNull + private static ClientTransaction newResumeTransaction(@NonNull Activity activity) { final ResumeActivityItem resumeStateRequest = ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */, false /* shouldSendCompatFakeFocus */); @@ -857,7 +859,8 @@ public class ActivityThreadTest { return transaction; } - private static ClientTransaction newStopTransaction(Activity activity) { + @NonNull + private static ClientTransaction newStopTransaction(@NonNull Activity activity) { final StopActivityItem stopStateRequest = StopActivityItem.obtain( activity.getActivityToken(), 0 /* configChanges */); @@ -867,8 +870,9 @@ public class ActivityThreadTest { return transaction; } - private static ClientTransaction newActivityConfigTransaction(Activity activity, - Configuration config) { + @NonNull + private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity, + @NonNull Configuration config) { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), config); @@ -878,8 +882,9 @@ public class ActivityThreadTest { return transaction; } - private static ClientTransaction newNewIntentTransaction(Activity activity, - List intents, boolean resume) { + @NonNull + private static ClientTransaction newNewIntentTransaction(@NonNull Activity activity, + @NonNull List intents, boolean resume) { final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents, resume); @@ -889,13 +894,14 @@ public class ActivityThreadTest { return transaction; } - private static ClientTransaction newTransaction(Activity activity) { - return newTransaction(activity.getActivityThread(), activity.getActivityToken()); + @NonNull + private static ClientTransaction newTransaction(@NonNull Activity activity) { + return newTransaction(activity.getActivityThread()); } - private static ClientTransaction newTransaction(ActivityThread activityThread, - @Nullable IBinder activityToken) { - return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken); + @NonNull + private static ClientTransaction newTransaction(@NonNull ActivityThread activityThread) { + return ClientTransaction.obtain(activityThread.getApplicationThread()); } // Test activity diff --git a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java index 08033cc4009c..785a8a1ced60 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java @@ -66,7 +66,7 @@ public class ActivityConfigurationChangeItemTest { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem .obtain(mToken, mConfiguration); - final Context context = item.getContextToUpdate(mHandler, mToken); + final Context context = item.getContextToUpdate(mHandler); assertEquals(mActivity, context); } diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java index 3d252fbddb80..531404bffd50 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java @@ -21,7 +21,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ClientTransactionHandler; -import android.os.IBinder; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -46,22 +45,21 @@ public class ClientTransactionTests { @Test public void testPreExecute() { - ClientTransactionItem callback1 = mock(ClientTransactionItem.class); - ClientTransactionItem callback2 = mock(ClientTransactionItem.class); - ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); - ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class); - IBinder token = mock(IBinder.class); + final ClientTransactionItem callback1 = mock(ClientTransactionItem.class); + final ClientTransactionItem callback2 = mock(ClientTransactionItem.class); + final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); + final ClientTransactionHandler clientTransactionHandler = + mock(ClientTransactionHandler.class); - ClientTransaction transaction = ClientTransaction.obtain(null /* client */, - token /* activityToken */); + final ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(callback1); transaction.addCallback(callback2); transaction.setLifecycleStateRequest(stateRequest); transaction.preExecute(clientTransactionHandler); - verify(callback1, times(1)).preExecute(clientTransactionHandler, token); - verify(callback2, times(1)).preExecute(clientTransactionHandler, token); - verify(stateRequest, times(1)).preExecute(clientTransactionHandler, token); + verify(callback1, times(1)).preExecute(clientTransactionHandler); + verify(callback2, times(1)).preExecute(clientTransactionHandler); + verify(stateRequest, times(1)).preExecute(clientTransactionHandler); } } diff --git a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java index 3926cfb14194..d9f5523c9782 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java @@ -24,7 +24,6 @@ import android.app.ActivityThread; import android.app.ClientTransactionHandler; import android.content.Context; import android.content.res.Configuration; -import android.os.IBinder; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -49,8 +48,6 @@ public class ConfigurationChangeItemTest { @Mock private ClientTransactionHandler mHandler; - @Mock - private IBinder mToken; // Can't mock final class. private final Configuration mConfiguration = new Configuration(); @@ -63,7 +60,7 @@ public class ConfigurationChangeItemTest { public void testGetContextToUpdate() { final ConfigurationChangeItem item = ConfigurationChangeItem .obtain(mConfiguration, DEVICE_ID_DEFAULT); - final Context context = item.getContextToUpdate(mHandler, mToken); + final Context context = item.getContextToUpdate(mHandler); assertEquals(ActivityThread.currentApplication(), context); } diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index c8d8f4be9e0a..4bbde0cd6366 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import android.app.ActivityOptions; +import android.app.IApplicationThread; import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -41,8 +42,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.function.Supplier; @@ -60,7 +64,15 @@ import java.util.function.Supplier; @Presubmit public class ObjectPoolTests { - private final IBinder mActivityToken = new Binder(); + @Mock + private IApplicationThread mApplicationThread; + @Mock + private IBinder mActivityToken; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } // 1. Check if two obtained objects from pool are not the same. // 2. Check if the state of the object is cleared after recycling. @@ -309,15 +321,15 @@ public class ObjectPoolTests { @Test public void testRecycleClientTransaction() { - ClientTransaction emptyItem = ClientTransaction.obtain(null, null); - ClientTransaction item = ClientTransaction.obtain(null, new Binder()); + ClientTransaction emptyItem = ClientTransaction.obtain(null); + ClientTransaction item = ClientTransaction.obtain(mApplicationThread); assertNotSame(item, emptyItem); assertNotEquals(item, emptyItem); item.recycle(); assertEquals(item, emptyItem); - ClientTransaction item2 = ClientTransaction.obtain(null, new Binder()); + ClientTransaction item2 = ClientTransaction.obtain(mApplicationThread); assertSame(item, item2); assertNotEquals(item2, emptyItem); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index a998b26b09c1..a1a2bdbe0f15 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; @@ -230,8 +231,7 @@ public class TransactionExecutorTests { when(stateRequest.getActivityToken()).thenReturn(token); when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class)); - ClientTransaction transaction = ClientTransaction.obtain(null /* client */, - token /* activityToken */); + ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(callback1); transaction.addCallback(callback2); transaction.setLifecycleStateRequest(stateRequest); @@ -240,8 +240,8 @@ public class TransactionExecutorTests { mExecutor.execute(transaction); InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest); - inOrder.verify(callback1).execute(eq(mTransactionHandler), eq(token), any()); - inOrder.verify(callback2).execute(eq(mTransactionHandler), eq(token), any()); + inOrder.verify(callback1).execute(eq(mTransactionHandler), any()); + inOrder.verify(callback2).execute(eq(mTransactionHandler), any()); inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any()); } @@ -254,8 +254,7 @@ public class TransactionExecutorTests { // An incoming destroy transaction enters binder thread (preExecute). final IBinder token = mock(IBinder.class); - final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */, - token /* activityToken */); + final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */); destroyTransaction.setLifecycleStateRequest( DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */)); destroyTransaction.preExecute(mTransactionHandler); @@ -263,8 +262,7 @@ public class TransactionExecutorTests { assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size()); // A previous queued launch transaction runs on main thread (execute). - final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */, - token /* activityToken */); + final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); final LaunchActivityItem launchItem = spy(new LaunchActivityItemBuilder().setActivityToken(token).build()); launchTransaction.addCallback(launchItem); @@ -272,7 +270,7 @@ public class TransactionExecutorTests { // The launch transaction should not be executed because its token is in the // to-be-destroyed container. - verify(launchItem, never()).execute(any(), any(), any()); + verify(launchItem, never()).execute(any(), any()); // After the destroy transaction has been executed, the token should be removed. mExecutor.execute(destroyTransaction); @@ -286,8 +284,7 @@ public class TransactionExecutorTests { PostExecItem postExecItem = new PostExecItem(ON_RESUME); IBinder token = mock(IBinder.class); - ClientTransaction transaction = ClientTransaction.obtain(null /* client */, - token /* activityToken */); + ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(postExecItem); // Verify resolution that should get to onPause @@ -439,7 +436,7 @@ public class TransactionExecutorTests { final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class); when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED); final IBinder token = mock(IBinder.class); - final ClientTransaction transaction = ClientTransaction.obtain(null /* client */, token); + final ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(activityItem); when(mTransactionHandler.getActivityClient(token)).thenReturn(null); @@ -449,7 +446,7 @@ public class TransactionExecutorTests { @Test public void testActivityItemExecute() { final IBinder token = mock(IBinder.class); - final ClientTransaction transaction = ClientTransaction.obtain(null /* client */, token); + final ClientTransaction transaction = ClientTransaction.obtain(null /* client */); final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class); when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED); when(activityItem.getActivityToken()).thenReturn(token); @@ -504,12 +501,12 @@ public class TransactionExecutorTests { private StubItem() { } - private StubItem(Parcel in) { + private StubItem(@NonNull Parcel in) { } @Override - public void execute(ClientTransactionHandler client, IBinder token, - PendingTransactionActions pendingActions) { + public void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { } @Override @@ -517,12 +514,11 @@ public class TransactionExecutorTests { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public StubItem createFromParcel(Parcel in) { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + public StubItem createFromParcel(@NonNull Parcel in) { return new StubItem(in); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index abc5d6b35b02..7d047c93520f 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -284,9 +284,7 @@ public class TransactionParcelTests { StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, 78 /* configChanges */); - Binder activityToken = new Binder(); - - ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(callback1); transaction.addCallback(callback2); transaction.setLifecycleStateRequest(lifecycleRequest); @@ -307,9 +305,7 @@ public class TransactionParcelTests { ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( mActivityToken, config()); - Binder activityToken = new Binder(); - - ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(callback1); transaction.addCallback(callback2); @@ -328,9 +324,7 @@ public class TransactionParcelTests { StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, 78 /* configChanges */); - Binder activityToken = new Binder(); - - ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.setLifecycleStateRequest(lifecycleRequest); writeAndPrepareForReading(transaction); diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java index db76d26b063e..a801a76d16c9 100644 --- a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java @@ -53,8 +53,6 @@ public class WindowContextInfoChangeItemTest { @Mock private ClientTransactionHandler mHandler; @Mock - private IBinder mToken; - @Mock private PendingTransactionActions mPendingActions; @Mock private IBinder mClientToken; @@ -72,7 +70,7 @@ public class WindowContextInfoChangeItemTest { public void testExecute() { final WindowContextInfoChangeItem item = WindowContextInfoChangeItem .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); - item.execute(mHandler, mToken, mPendingActions); + item.execute(mHandler, mPendingActions); verify(mHandler).handleWindowContextInfoChanged(mClientToken, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY)); @@ -84,7 +82,7 @@ public class WindowContextInfoChangeItemTest { final WindowContextInfoChangeItem item = WindowContextInfoChangeItem .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); - final Context context = item.getContextToUpdate(mHandler, mToken); + final Context context = item.getContextToUpdate(mHandler); assertEquals(mWindowContext, context); } diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java index 17e0ebc1edbc..cf9935f2822f 100644 --- a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java @@ -45,8 +45,6 @@ public class WindowContextWindowRemovalItemTest { @Mock private ClientTransactionHandler mHandler; @Mock - private IBinder mToken; - @Mock private PendingTransactionActions mPendingActions; @Mock private IBinder mClientToken; @@ -60,7 +58,7 @@ public class WindowContextWindowRemovalItemTest { public void testExecute() { final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain( mClientToken); - item.execute(mHandler, mToken, mPendingActions); + item.execute(mHandler, mPendingActions); verify(mHandler).handleWindowContextWindowRemoval(mClientToken); } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 4c9ec9d863bb..3c8e630dba2b 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1018,8 +1018,7 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain( - r.app.getThread(), r.token); + final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); transaction.addCallback(EnterPipRequestedItem.obtain(r.token)); mService.getLifecycleManager().scheduleTransaction(transaction); return true; @@ -1040,8 +1039,7 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain( - r.app.getThread(), r.token); + final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); transaction.addCallback(PipStateTransactionItem.obtain(r.token, pipState)); mService.getLifecycleManager().scheduleTransaction(transaction); } catch (Exception e) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index dca2b6f8f2af..f42dc2564be6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1442,7 +1442,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId, config); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), MoveToDisplayItem.obtain(token, displayId, config)); } catch (RemoteException e) { // If process died, whatever. @@ -1459,7 +1459,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, " + "config: %s", this, config); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), ActivityConfigurationChangeItem.obtain(token, config)); } catch (RemoteException e) { // If process died, whatever. @@ -1480,7 +1480,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b", this, onTop); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), TopResumedActivityChangeItem.obtain(token, onTop)); } catch (RemoteException e) { // If process died, whatever. @@ -2727,7 +2727,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } try { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), TransferSplashScreenViewStateItem.obtain(token, parcelable, windowAnimationLeash)); scheduleTransferSplashScreenTimeout(); @@ -3894,7 +3894,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), DestroyActivityItem.obtain(token, finishing, configChangeFlags)); } catch (Exception e) { // We can just ignore exceptions here... if the process has crashed, our death @@ -4800,9 +4800,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (isState(RESUMED) && attachedToProcess()) { try { - final ArrayList list = new ArrayList(); + final ArrayList list = new ArrayList<>(); list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), ActivityResultItem.obtain(token, list)); return; } catch (Exception e) { @@ -4813,7 +4813,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Schedule sending results now for Media Projection setup. if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED, STOPPING, STOPPED)) { - final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token); + final ClientTransaction transaction = ClientTransaction.obtain(app.getThread()); // Build result to be returned immediately. transaction.addCallback(ActivityResultItem.obtain( token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data)))); @@ -4908,7 +4908,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Making sure the client state is RESUMED after transaction completed and doing // so only if activity is currently RESUMED. Otherwise, client may have extra // life-cycle calls to RESUMED (and PAUSED later). - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), NewIntentItem.obtain(token, ar, mState == RESUMED)); unsent = false; } catch (RemoteException e) { @@ -6143,7 +6143,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this), shortComponentName, "userLeaving=false", "make-active"); try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), PauseActivityItem.obtain(token, finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */, mAutoEnteringPip)); } catch (Exception e) { @@ -6156,7 +6156,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setState(STARTED, "makeActiveIfNeeded"); try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), StartActivityItem.obtain(token, takeOptions())); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e); @@ -6454,7 +6454,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } EventLogTags.writeWmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), StopActivityItem.obtain(token, configChangeFlags)); mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT); @@ -9882,7 +9882,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { lifecycleItem = PauseActivityItem.obtain(token); } - final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token); + final ClientTransaction transaction = ClientTransaction.obtain(app.getThread()); transaction.addCallback(callbackItem); transaction.setLifecycleStateRequest(lifecycleItem); mAtmService.getLifecycleManager().scheduleTransaction(transaction); @@ -9976,7 +9976,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The process will be killed until the activity reports stopped with saved state (see // {@link ActivityTaskManagerService.activityStopped}). try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), StopActivityItem.obtain(token, 0 /* configChanges */)); } catch (RemoteException e) { Slog.w(TAG, "Exception thrown during restart " + this, e); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index e523119ee9a0..7266a1439ffe 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -923,7 +923,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Create activity launch transaction. final ClientTransaction clientTransaction = ClientTransaction.obtain( - proc.getThread(), r.token); + proc.getThread()); final boolean isTransitionForward = r.isTransitionForward(); final IBinder fragmentToken = r.getTaskFragment().getFragmentToken(); diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index 7430f0f1dc47..ef31837f810b 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -18,11 +18,10 @@ package com.android.server.wm; import android.annotation.NonNull; import android.app.IApplicationThread; +import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; -import android.app.servertransaction.ActivityLifecycleItem; import android.os.Binder; -import android.os.IBinder; import android.os.RemoteException; /** @@ -36,13 +35,13 @@ class ClientLifecycleManager { // TODO(lifecycler): Use object pools for transactions and transaction items. /** - * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request. + * Schedules a transaction, which may consist of multiple callbacks and a lifecycle request. * @param transaction A sequence of client transaction items. * @throws RemoteException * * @see ClientTransaction */ - void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException { final IApplicationThread client = transaction.getClient(); transaction.schedule(); if (!(client instanceof Binder)) { @@ -54,75 +53,22 @@ class ClientLifecycleManager { } /** - * Schedule a single lifecycle request or callback to client activity. - * @param client Target client. - * @param activityToken Target activity token. - * @param stateRequest A request to move target activity to a desired lifecycle state. - * @throws RemoteException - * - * @see ClientTransactionItem - */ - void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, - @NonNull ActivityLifecycleItem stateRequest) throws RemoteException { - final ClientTransaction clientTransaction = transactionWithState(client, activityToken, - stateRequest); - scheduleTransaction(clientTransaction); - } - - /** - * Schedule a single callback delivery to client activity. + * Schedules a single transaction item, either a callback or a lifecycle request, delivery to + * client application. * @param client Target client. - * @param activityToken Target activity token. - * @param callback A request to deliver a callback. - * @throws RemoteException - * - * @see ClientTransactionItem - */ - void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, - @NonNull ClientTransactionItem callback) throws RemoteException { - final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken, - callback); - scheduleTransaction(clientTransaction); - } - - /** - * Schedule a single callback delivery to client application. - * @param client Target client. - * @param callback A request to deliver a callback. + * @param transactionItem A transaction item to deliver a message. * @throws RemoteException * * @see ClientTransactionItem */ void scheduleTransaction(@NonNull IApplicationThread client, - @NonNull ClientTransactionItem callback) throws RemoteException { - final ClientTransaction clientTransaction = transactionWithCallback(client, - null /* activityToken */, callback); + @NonNull ClientTransactionItem transactionItem) throws RemoteException { + final ClientTransaction clientTransaction = ClientTransaction.obtain(client); + if (transactionItem instanceof ActivityLifecycleItem) { + clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); + } else { + clientTransaction.addCallback(transactionItem); + } scheduleTransaction(clientTransaction); } - - /** - * @return A new instance of {@link ClientTransaction} with a single lifecycle state request. - * - * @see ClientTransaction - * @see ClientTransactionItem - */ - private static ClientTransaction transactionWithState(@NonNull IApplicationThread client, - @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) { - final ClientTransaction clientTransaction = ClientTransaction.obtain(client, activityToken); - clientTransaction.setLifecycleStateRequest(stateRequest); - return clientTransaction; - } - - /** - * @return A new instance of {@link ClientTransaction} with a single callback invocation. - * - * @see ClientTransaction - * @see ClientTransactionItem - */ - private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client, - IBinder activityToken, @NonNull ClientTransactionItem callback) { - final ClientTransaction clientTransaction = ClientTransaction.obtain(client, activityToken); - clientTransaction.addCallback(callback); - return clientTransaction; - } } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index c4ed0dd9d606..534cdc2015e3 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -227,7 +227,7 @@ final class DisplayRotationCompatPolicy { "Refreshing activity for camera compatibility treatment, " + "activityRecord=%s", activity); final ClientTransaction transaction = ClientTransaction.obtain( - activity.app.getThread(), activity.token); + activity.app.getThread()); transaction.addCallback(RefreshCallbackItem.obtain(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE)); transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(activity.token, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 57f44cb599fe..1da5cc0cb865 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1456,8 +1456,8 @@ class TaskFragment extends WindowContainer { } try { - final ClientTransaction transaction = - ClientTransaction.obtain(next.app.getThread(), next.token); + final ClientTransaction transaction = ClientTransaction.obtain( + next.app.getThread()); // Deliver all pending results. ArrayList a = next.results; if (a != null) { @@ -1726,7 +1726,7 @@ class TaskFragment extends WindowContainer { prev.shortComponentName, "userLeaving=" + userLeaving, reason); mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), - prev.token, PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving, + PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving, prev.configChangeFlags, pauseImmediately, autoEnteringPip)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ae587003c1c2..e4a9cdda0b64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -489,7 +489,7 @@ public class ActivityRecordTests extends WindowTestsBase { ensureActivityConfiguration(activity); verify(mAtm.getLifecycleManager(), never()) - .scheduleTransaction(any(), any(), isA(ActivityConfigurationChangeItem.class)); + .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class)); } @Test @@ -519,7 +519,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); verify(mAtm.getLifecycleManager()).scheduleTransaction( - eq(activity.app.getThread()), eq(activity.token), eq(expected)); + eq(activity.app.getThread()), eq(expected)); } @Test @@ -599,7 +599,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()), - eq(activity.token), eq(expected)); + eq(expected)); verify(displayRotation).onSetRequestedOrientation(); } @@ -817,7 +817,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); verify(mAtm.getLifecycleManager()).scheduleTransaction( - eq(activity.app.getThread()), eq(activity.token), eq(expected)); + eq(activity.app.getThread()), eq(expected)); } finally { stack.getDisplayArea().removeChild(stack); } @@ -1787,9 +1787,10 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); final WindowProcessController wpc = activity.app; setup.accept(activity); + clearInvocations(mAtm.getLifecycleManager()); activity.getTask().removeImmediately("test"); try { - verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.token), + verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), isA(DestroyActivityItem.class)); } catch (RemoteException ignored) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java index 28dd458a786c..a18dbaf39575 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java @@ -23,7 +23,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import android.app.IApplicationThread; import android.app.servertransaction.ClientTransaction; -import android.os.Binder; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -40,8 +39,7 @@ public class ClientLifecycleManagerTests { @Test public void testScheduleAndRecycleBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class), - new Binder())); + ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class))); ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); clientLifecycleManager.scheduleTransaction(item); @@ -51,8 +49,7 @@ public class ClientLifecycleManagerTests { @Test public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class), - new Binder())); + ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class))); ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); clientLifecycleManager.scheduleTransaction(item); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 1b44c01bfab5..2af67452283b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -571,8 +571,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) .setIsRefreshAfterRotationRequested(true); - final ClientTransaction transaction = ClientTransaction.obtain( - mActivity.app.getThread(), mActivity.token); + final ClientTransaction transaction = ClientTransaction.obtain(mActivity.app.getThread()); transaction.addCallback(RefreshCallbackItem.obtain(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE)); transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(mActivity.token, diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index d85d9b5729a0..2d5b72b3c680 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -111,7 +111,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { return null; } final WindowContextInfoChangeItem infoChangeItem = (WindowContextInfoChangeItem) item; - infoChangeItem.execute(mHandler, null, null); + infoChangeItem.execute(mHandler, null /* pendingActions */); return null; }).when(mWpc).scheduleClientTransactionItem(any()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index ebe40b05162d..b89182d728ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -330,7 +330,7 @@ public class WindowProcessControllerTests extends WindowTestsBase { ArgumentCaptor.forClass(ConfigurationChangeItem.class); verify(clientManager).scheduleTransaction(eq(thread), captor.capture()); final ClientTransactionHandler client = mock(ClientTransactionHandler.class); - captor.getValue().preExecute(client, null /* token */); + captor.getValue().preExecute(client); final ArgumentCaptor configCaptor = ArgumentCaptor.forClass(Configuration.class); verify(client).updatePendingConfiguration(configCaptor.capture()); -- GitLab From eaa129138096bc00b663bca93a5af9786aa47154 Mon Sep 17 00:00:00 2001 From: Beverly Tai Date: Thu, 14 Sep 2023 20:51:56 +0000 Subject: [PATCH 07/95] Revert "On device lockdown, always show the keyguard" This reverts commit c851ef6d27bf7d3193c85170706a67fb6185981a. Reason for revert: b/300463732 regression Bug: 300463732 Bug: 218495634 Change-Id: I314e5615b798487d9a965eaa0937905b65aa85fc Merged-In: I314e5615b798487d9a965eaa0937905b65aa85fc --- .../keyguard/KeyguardViewMediator.java | 12 ++------- .../keyguard/KeyguardViewMediatorTest.java | 25 ------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 18474e0af5b8..015918884f2e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -768,13 +768,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } } - - @Override - public void onStrongAuthStateChanged(int userId) { - if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { - doKeyguardLocked(null); - } - } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -2159,9 +2152,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Enable the keyguard if the settings are appropriate. */ private void doKeyguardLocked(Bundle options) { - // if another app is disabling us, don't show unless we're in lockdown mode - if (!mExternallyEnabled - && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + // if another app is disabling us, don't show + if (!mExternallyEnabled) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 1c8241433172..cfe3b0d2a803 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -157,8 +157,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; private @Mock ShadeWindowLogger mShadeWindowLogger; - private @Captor ArgumentCaptor - mKeyguardUpdateMonitorCallbackCaptor; private @Captor ArgumentCaptor mKeyguardStateControllerCallback; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); @@ -205,25 +203,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { createAndStartViewMediator(); } - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() { - // GIVEN keyguard is not enabled and isn't showing - mViewMediator.onSystemReady(); - mViewMediator.setKeyguardEnabled(false); - TestableLooper.get(this).processAllMessages(); - captureKeyguardUpdateMonitorCallback(); - assertFalse(mViewMediator.isShowingAndNotOccluded()); - - // WHEN lockdown occurs - when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0); - - // THEN keyguard is shown - TestableLooper.get(this).processAllMessages(); - assertTrue(mViewMediator.isShowingAndNotOccluded()); - } - @Test public void testOnGoingToSleep_UpdatesKeyguardGoingAway() { mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); @@ -844,10 +823,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); } - private void captureKeyguardUpdateMonitorCallback() { - verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture()); - } - private void captureKeyguardStateControllerCallback() { verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture()); } -- GitLab From 2d4b4e46709be968c7fb22d3f42e91cc279eefd6 Mon Sep 17 00:00:00 2001 From: Fiona Campbell Date: Mon, 18 Sep 2023 17:01:29 +0000 Subject: [PATCH 08/95] Fix testDisplayInputFlags - Create local display device to fix this test. Bug: 288880734 Test: atest LogicalDisplayTest Change-Id: I84c4e5cb6fb143c16913688b81373153f00c90ad --- .../server/display/LogicalDisplayTest.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index c0128ae38a28..1c43418f9276 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -28,7 +28,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; +import android.content.Context; import android.graphics.Point; +import android.os.IBinder; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; @@ -40,7 +42,6 @@ import androidx.test.filters.SmallTest; import com.android.server.display.layout.Layout; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import java.io.InputStream; @@ -56,6 +57,9 @@ public class LogicalDisplayTest { private LogicalDisplay mLogicalDisplay; private DisplayDevice mDisplayDevice; + private DisplayAdapter mDisplayAdapter; + private Context mContext; + private IBinder mDisplayToken; private DisplayDeviceRepository mDeviceRepo; private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo(); @@ -64,6 +68,9 @@ public class LogicalDisplayTest { // Share classloader to allow package private access. System.setProperty("dexmaker.share_classloader", "true"); mDisplayDevice = mock(DisplayDevice.class); + mDisplayAdapter = mock(DisplayAdapter.class); + mContext = mock(Context.class); + mDisplayToken = mock(IBinder.class); mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo()); @@ -131,33 +138,43 @@ public class LogicalDisplayTest { assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); } - // TODO: b/288880734 - fix test after display tests migration @Test - @Ignore public void testDisplayInputFlags() { + DisplayDevice displayDevice = new DisplayDevice(mDisplayAdapter, mDisplayToken, + "unique_display_id", mContext) { + @Override + public boolean hasStableUniqueId() { + return false; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return mDisplayDeviceInfo; + } + }; SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); - mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + mLogicalDisplay.configureDisplayLocked(t, displayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE; - mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + mLogicalDisplay.configureDisplayLocked(t, displayDevice, false); verify(t).setDisplayFlags(any(), eq(0)); reset(t); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL; - mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + mLogicalDisplay.configureDisplayLocked(t, displayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); mLogicalDisplay.setEnabledLocked(false); - mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + mLogicalDisplay.configureDisplayLocked(t, displayDevice, false); verify(t).setDisplayFlags(any(), eq(0)); reset(t); mLogicalDisplay.setEnabledLocked(true); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; - mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + mLogicalDisplay.configureDisplayLocked(t, displayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); } -- GitLab From 760f1e4f5657b3bd5126c8d5886e99f16885c735 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Thu, 7 Sep 2023 15:47:55 -0700 Subject: [PATCH 09/95] Accept APK install with v4 signature to set up fs-verity .idsig is recognized and staged in the installer session. When .idsig is provided, fs-verity is enabled in validateApkInstallLocked before the first APK signature check happens. With fs-verity enabled, ApkSignatureSchemeV4Verifier can also work (in additional to IncFS) over fs-verity. The verifier can build fs-verity digest from V4Signature.HashingInfo and verify the signed data is consistent with the actual fs-verity digest. See VerityUtils#generateFsVerityDigest. ApkSignatureSchemeV4Verifier#extractSignature now also throws SignatureException. When a signature size is wrong (see CTS test PkgInstallSignatureVerificationTest#testInstallV4WithWrongSignatureBytesSize), V4Signature.SigningInfos.fromByteArray throws an EOFException (which is an IOException). The IOException is handled as missing signature by rethrowing as SignatureNotFoundException. But this allows a fallback to other v3/v2 signature check. This change distriguishes it by rethrowing a SignatureException instead. This is not a problem during an incremental install, because the signature size check happens earlier when the installer commits, and it's done inside IncFS. Bug: 277344944 Test: Force enable the (read-only) flag, since it's off in build time, then atest android.appsecurity.cts.PkgInstallSignatureVerificationTest Change-Id: I6fd22fe2e04cfc58c68e690f23f63ff268938eda --- .../android/os/incremental/V4Signature.java | 5 +- .../apk/ApkSignatureSchemeV4Verifier.java | 66 +++++++++++++++---- .../internal/security/VerityUtils.java | 37 ++++++++++- .../AppIntegrityManagerServiceImpl.java | 1 + .../com/android/server/pm/ApkChecksums.java | 2 +- .../server/pm/PackageInstallerSession.java | 51 ++++++++++++++ 6 files changed, 147 insertions(+), 15 deletions(-) diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 2044502b10e8..38d885f503c5 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -254,7 +254,10 @@ public class V4Signature { this.signingInfos = signingInfos; } - private static V4Signature readFrom(InputStream stream) throws IOException { + /** + * Constructs a V4Signature from an InputStream. + */ + public static V4Signature readFrom(InputStream stream) throws IOException { final int version = readIntLE(stream); int maxSize = INCFS_MAX_SIGNATURE_SIZE; final byte[] hashingInfo = readBytes(stream, maxSize); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index 6b26155eced4..52102714eb5f 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -27,9 +27,14 @@ import android.os.incremental.V4Signature; import android.util.ArrayMap; import android.util.Pair; +import com.android.internal.security.VerityUtils; + import java.io.ByteArrayInputStream; +import java.io.EOFException; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -60,7 +65,7 @@ public class ApkSignatureSchemeV4Verifier { * certificates associated with each signer. */ public static VerifiedSigner extractCertificates(String apkFile) - throws SignatureNotFoundException, SecurityException { + throws SignatureNotFoundException, SignatureException, SecurityException { Pair pair = extractSignature(apkFile); return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT); } @@ -69,15 +74,37 @@ public class ApkSignatureSchemeV4Verifier { * Extracts APK Signature Scheme v4 signature of the provided APK. */ public static Pair extractSignature( - String apkFile) throws SignatureNotFoundException { - final File apk = new File(apkFile); - final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( - apk.getAbsolutePath()); - if (signatureBytes == null || signatureBytes.length == 0) { - throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS."); - } + String apkFile) throws SignatureNotFoundException, SignatureException { try { - final V4Signature signature = V4Signature.readFrom(signatureBytes); + final File apk = new File(apkFile); + boolean needsConsistencyCheck; + + // 1. Try IncFS first. IncFS verifies the file according to the integrity metadata + // (including the root hash of Merkle tree) it keeps track of with signature check. No + // further consistentcy check is needed. + byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( + apk.getAbsolutePath()); + V4Signature signature; + if (signatureBytes != null && signatureBytes.length > 0) { + needsConsistencyCheck = false; + signature = V4Signature.readFrom(signatureBytes); + } else if (android.security.Flags.extendVbChainToUpdatedApk()) { + // 2. Try fs-verity next. fs-verity checks against the Merkle tree, but the + // v4 signature file (including a raw root hash) is managed separately. We need to + // ensure the signed data from the file is consistent with the actual file. + needsConsistencyCheck = true; + + final File idsig = new File(apk.getAbsolutePath() + V4Signature.EXT); + try (var fis = new FileInputStream(idsig.getAbsolutePath())) { + signature = V4Signature.readFrom(fis); + } catch (IOException e) { + throw new SignatureNotFoundException( + "Failed to obtain signature bytes from .idsig"); + } + } else { + throw new SignatureNotFoundException( + "Failed to obtain signature bytes from IncFS."); + } if (!signature.isVersionSupported()) { throw new SecurityException( "v4 signature version " + signature.version + " is not supported"); @@ -86,9 +113,26 @@ public class ApkSignatureSchemeV4Verifier { signature.hashingInfo); final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray( signature.signingInfos); + + if (needsConsistencyCheck) { + final byte[] actualDigest = VerityUtils.getFsverityDigest(apk.getAbsolutePath()); + if (actualDigest == null) { + throw new SecurityException("The APK does not have fs-verity"); + } + final byte[] computedDigest = + VerityUtils.generateFsVerityDigest(apk.length(), hashingInfo); + if (!Arrays.equals(computedDigest, actualDigest)) { + throw new SignatureException("Actual digest does not match the v4 signature"); + } + } + return Pair.create(hashingInfo, signingInfos); + } catch (EOFException e) { + throw new SignatureException("V4 signature is invalid.", e); } catch (IOException e) { throw new SignatureNotFoundException("Failed to read V4 signature.", e); + } catch (DigestException | NoSuchAlgorithmException e) { + throw new SecurityException("Failed to calculate the digest", e); } } @@ -107,7 +151,7 @@ public class ApkSignatureSchemeV4Verifier { signingInfo); final Pair result = verifySigner(signingInfo, signedData); - // Populate digests enforced by IncFS driver. + // Populate digests enforced by IncFS driver and fs-verity. Map contentDigests = new ArrayMap<>(); contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm), hashingInfo.rawRootHash); @@ -217,7 +261,7 @@ public class ApkSignatureSchemeV4Verifier { public final byte[] apkDigest; // Algorithm -> digest map of signed digests in the signature. - // These are continuously enforced by the IncFS driver. + // These are continuously enforced by the IncFS driver and fs-verity. public final Map contentDigests; public VerifiedSigner(Certificate[] certs, byte[] apkDigest, diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java index 74a9d16c890d..7f7ea8b28546 100644 --- a/core/java/com/android/internal/security/VerityUtils.java +++ b/core/java/com/android/internal/security/VerityUtils.java @@ -17,8 +17,10 @@ package com.android.internal.security; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Build; import android.os.SystemProperties; +import android.os.incremental.V4Signature; import android.system.Os; import android.system.OsConstants; import android.util.Slog; @@ -40,6 +42,9 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -192,9 +197,9 @@ public abstract class VerityUtils { * * @see * File digest computation in Linux kernel documentation - * @return Bytes of fs-verity digest + * @return Bytes of fs-verity digest, or null if the file does not have fs-verity enabled */ - public static byte[] getFsverityDigest(@NonNull String filePath) { + public static @Nullable byte[] getFsverityDigest(@NonNull String filePath) { byte[] result = new byte[HASH_SIZE_BYTES]; int retval = measureFsverityNative(filePath, result); if (retval < 0) { @@ -206,6 +211,34 @@ public abstract class VerityUtils { return result; } + /** + * Generates an fs-verity digest from a V4Signature.HashingInfo and the file's size. + */ + public static @NonNull byte[] generateFsVerityDigest(long fileSize, + @NonNull V4Signature.HashingInfo hashingInfo) + throws DigestException, NoSuchAlgorithmException { + if (hashingInfo.rawRootHash == null || hashingInfo.rawRootHash.length != 32) { + throw new IllegalArgumentException("Expect a 32-byte rootHash for SHA256"); + } + if (hashingInfo.log2BlockSize != 12) { + throw new IllegalArgumentException( + "Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); + } + + var buffer = ByteBuffer.allocate(256); // sizeof(fsverity_descriptor) + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put((byte) 1); // version + buffer.put((byte) 1); // Merkle tree hash algorithm, 1 for SHA256 + buffer.put(hashingInfo.log2BlockSize); // log2(block-size), only log2(4096) is supported + buffer.put((byte) 0); // size of salt in bytes; 0 if none + buffer.putInt(0); // reserved, must be 0 + buffer.putLong(fileSize); // size of file the Merkle tree is built over + buffer.put(hashingInfo.rawRootHash); // Merkle tree root hash + // The rest are zeros, including the latter half of root hash unused for SHA256. + + return MessageDigest.getInstance("SHA-256").digest(buffer.array()); + } + /** @hide */ @VisibleForTesting public static byte[] toFormattedDigest(byte[] digest) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index a7c986d04fa4..ec858ee296e1 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -513,6 +513,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { List apkFiles = filesList .map(path -> path.toAbsolutePath().toString()) + .filter(str -> str.endsWith(".apk")) .collect(Collectors.toList()); sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles); } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index 5b93244c1710..50ed3b1859df 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -655,7 +655,7 @@ public class ApkChecksums { } } catch (SignatureNotFoundException e) { // Nothing - } catch (SecurityException e) { + } catch (SignatureException | SecurityException e) { Slog.e(TAG, "V4 signature error", e); } return null; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 2e9da099b86c..3364b671fdb1 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -137,6 +137,7 @@ import android.os.incremental.IncrementalFileStorages; import android.os.incremental.IncrementalManager; import android.os.incremental.PerUidReadTimeouts; import android.os.incremental.StorageHealthCheckParams; +import android.os.incremental.V4Signature; import android.os.storage.StorageManager; import android.provider.DeviceConfig; import android.provider.Settings.Global; @@ -801,6 +802,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // entries like "lost+found". if (file.isDirectory()) return false; if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false; + if (file.getName().endsWith(V4Signature.EXT)) return false; if (isAppMetadata(file)) return false; if (DexMetadataHelper.isDexMetadataFile(file)) return false; if (VerityUtils.isFsveritySignatureFile(file)) return false; @@ -1505,6 +1507,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return filterFiles(stageDir, names, sAddedApkFilter); } + @GuardedBy("mLock") + private void enableFsVerityToAddedApksWithIdsig() throws PackageManagerException { + try { + List files = getAddedApksLocked(); + for (var file : files) { + if (new File(file.getPath() + V4Signature.EXT).exists()) { + VerityUtils.setUpFsverity(file.getPath()); + } + } + } catch (IOException e) { + throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, + "Failed to enable fs-verity to verify with idsig: " + e); + } + } + @GuardedBy("mLock") private List getAddedApkLitesLocked() throws PackageManagerException { if (!isArchivedInstallation()) { @@ -3294,6 +3311,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + // Needs to happen before the first v4 signature verification, which happens in + // getAddedApkLitesLocked. + if (android.security.Flags.extendVbChainToUpdatedApk()) { + if (!isIncrementalInstallation()) { + enableFsVerityToAddedApksWithIdsig(); + } + } + final List addedFiles = getAddedApkLitesLocked(); if (addedFiles.isEmpty() && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) { @@ -3656,6 +3681,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") + private void maybeStageV4SignatureLocked(File origFile, File targetFile) + throws PackageManagerException { + final File originalSignature = new File(origFile.getPath() + V4Signature.EXT); + if (originalSignature.exists()) { + final File stagedSignature = new File(targetFile.getPath() + V4Signature.EXT); + stageFileLocked(originalSignature, stagedSignature); + } + } + @GuardedBy("mLock") private void maybeStageDexMetadataLocked(File origFile, File targetFile) throws PackageManagerException { @@ -3783,6 +3818,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Stage APK's fs-verity signature if present. maybeStageFsveritySignatureLocked(origFile, targetFile, isFsVerityRequiredForApk(origFile, targetFile)); + // Stage APK's v4 signature if present. + if (android.security.Flags.extendVbChainToUpdatedApk()) { + maybeStageV4SignatureLocked(origFile, targetFile); + } // Stage dex metadata (.dm) and corresponding fs-verity signature if present. maybeStageDexMetadataLocked(origFile, targetFile); // Stage checksums (.digests) if present. @@ -3799,11 +3838,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") + private void maybeInheritV4SignatureLocked(File origFile) { + // Inherit the v4 signature file if present. + final File v4SignatureFile = new File(origFile.getPath() + V4Signature.EXT); + if (v4SignatureFile.exists()) { + mResolvedInheritedFiles.add(v4SignatureFile); + } + } + @GuardedBy("mLock") private void inheritFileLocked(File origFile) { mResolvedInheritedFiles.add(origFile); maybeInheritFsveritySignatureLocked(origFile); + if (android.security.Flags.extendVbChainToUpdatedApk()) { + maybeInheritV4SignatureLocked(origFile); + } // Inherit the dex metadata if present. final File dexMetadataFile = -- GitLab From 3054e71ec1e57347490d5b42bf1affd06bd5e1d4 Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Mon, 18 Sep 2023 21:12:56 -0700 Subject: [PATCH 10/95] Move the binder proxy accounting to libbinder So the android.os.Debug#getBinderProxyObjectCount will include the binder proxies from both of the Java and native layers. Bug: 298314844 Test: dumpsys meminfo Change-Id: Ifb57eeacc0a4b6ee6e164e83f6f843bfe36f2b15 --- core/jni/android_util_Binder.cpp | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 6ed0a8a047f5..041f9c7edeef 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -158,12 +158,8 @@ static struct thread_dispatch_offsets_t // **************************************************************************** // **************************************************************************** -static constexpr int32_t PROXY_WARN_INTERVAL = 5000; static constexpr uint32_t GC_INTERVAL = 1000; -static std::atomic gNumProxies(0); -static std::atomic gProxiesWarned(0); - // Number of GlobalRefs held by JavaBBinders. static std::atomic gNumLocalRefsCreated(0); static std::atomic gNumLocalRefsDeleted(0); @@ -776,19 +772,7 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp& val) return NULL; } BinderProxyNativeData* actualNativeData = getBPNativeData(env, object); - if (actualNativeData == nativeData) { - // Created a new Proxy - uint32_t numProxies = gNumProxies.fetch_add(1, std::memory_order_relaxed); - uint32_t numLastWarned = gProxiesWarned.load(std::memory_order_relaxed); - if (numProxies >= numLastWarned + PROXY_WARN_INTERVAL) { - // Multiple threads can get here, make sure only one of them gets to - // update the warn counter. - if (gProxiesWarned.compare_exchange_strong(numLastWarned, - numLastWarned + PROXY_WARN_INTERVAL, std::memory_order_relaxed)) { - ALOGW("Unexpectedly many live BinderProxies: %d\n", numProxies); - } - } - } else { + if (actualNativeData != nativeData) { delete nativeData; } @@ -1143,7 +1127,7 @@ jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz) jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz) { - return gNumProxies.load(); + return BpBinder::getBinderProxyCount(); } jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz) @@ -1428,7 +1412,6 @@ static void BinderProxy_destroy(void* rawNativeData) nativeData->mObject.get(), nativeData->mOrgue.get()); delete nativeData; IPCThreadState::self()->flushCommands(); - --gNumProxies; } JNIEXPORT jlong JNICALL android_os_BinderProxy_getNativeFinalizer(JNIEnv*, jclass) { -- GitLab From 6c2e46027808f117b6d25587773c5581e4e9670d Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 19 Sep 2023 17:27:44 +0000 Subject: [PATCH 11/95] Fix NPE in getShortcutIconDrawable Bug: 301007443 Test: manual Change-Id: I6f42d921fbdde4a33eae66e15325ac399ed7d52e --- core/java/android/content/pm/LauncherApps.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 3165e292166b..dbaa4c93d71c 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -75,7 +75,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; -import java.io.FileNotFoundException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1424,8 +1423,8 @@ public class LauncherApps { } try { return mContext.getContentResolver().openFileDescriptor(Uri.parse(uri), "r"); - } catch (FileNotFoundException e) { - Log.e(TAG, "Icon file not found: " + uri); + } catch (Exception e) { + Log.e(TAG, "Failed to open icon file: " + uri, e); return null; } } -- GitLab From 70719529eb57dd5505c9840a0211a582b93a2495 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Mon, 11 Sep 2023 21:55:10 -0700 Subject: [PATCH 12/95] Refactor how split ratios are stored and passed between Launcher and Shell Split ratios are now stored as an int enum called @SnapPosition. For example, there is a snap position representing a 50-50 split, one for a 70-30 split, and so on. Previously, split ratios were calculated once by the DividerSnapAlgorithm and stored only as int positions on each SnapTarget, meaning that if you wanted to know the screen split ratio, you would have to calculate it manually from the bounds of each RemoteAnimationTarget and pass it around as a float (differing across devices with different screen sizes). With SnapPosition, screen ratios are more available and less device dependent. The change should lead to cleaner (and hopefully more readable) code. - SnapTargets now know their snapPosition in addition to their pixel position - Launcher now requests split launches using the snapPosition rather than a float splitRatio Bug: 274189428 Bug: 182839788 Test: Existing tests still pass Change-Id: I50d109ee67993629c277ec65d42b68fae18082b3 --- .../common/split/DividerSnapAlgorithm.java | 39 +++++++-- .../wm/shell/common/split/SplitLayout.java | 47 ++++++---- .../wm/shell/splitscreen/ISplitScreen.aidl | 16 ++-- .../splitscreen/SplitScreenController.java | 86 ++++++++++--------- .../shell/splitscreen/StageCoordinator.java | 67 ++++++++------- .../android/wm/shell/util/SplitBounds.java | 17 ++-- .../shell/common/split/SplitLayoutTests.java | 6 +- .../recents/GroupedRecentTaskInfoTest.kt | 4 +- .../recents/RecentTasksControllerTest.java | 20 +++-- .../wm/shell/recents/SplitBoundsTest.java | 14 +-- 10 files changed, 192 insertions(+), 124 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index a5000feae239..f9a286ec804f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; + import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70; import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30; @@ -36,6 +37,8 @@ import android.hardware.display.DisplayManager; import android.view.Display; import android.view.DisplayInfo; +import androidx.annotation.Nullable; + import java.util.ArrayList; /** @@ -203,6 +206,21 @@ public class DividerSnapAlgorithm { } } + /** + * Gets the SnapTarget corresponding to the given {@link SnapPosition}, or null if no such + * SnapTarget exists. + */ + @Nullable + public SnapTarget findSnapTarget(@SnapPosition int snapPosition) { + for (SnapTarget t : mTargets) { + if (t.snapPosition == snapPosition) { + return t; + } + } + + return null; + } + public float calculateDismissingFraction(int position) { if (position < mFirstSplitTarget.position) { return 1f - (float) (position - getStartInset()) @@ -356,9 +374,9 @@ public class DividerSnapAlgorithm { * Adds a target at {@param position} but only if the area with size of {@param smallerSize} * meets the minimal size requirement. */ - private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapTo) { + private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { if (smallerSize >= mMinimalSizeResizableTask) { - mTargets.add(new SnapTarget(position, position, snapTo)); + mTargets.add(new SnapTarget(position, position, snapPosition)); } } @@ -418,6 +436,13 @@ public class DividerSnapAlgorithm { return mLastSplitTarget != mMiddleTarget; } + /** + * Finds the {@link SnapPosition} nearest to the given position. + */ + public int calculateNearestSnapPosition(int currentPosition) { + return snap(currentPosition, /* hardDismiss */ true).snapPosition; + } + /** * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left * if {@param increment} is negative and moves right otherwise. @@ -454,7 +479,7 @@ public class DividerSnapAlgorithm { /** * An int describing the placement of the divider in this snap target. */ - public final @SnapPosition int snapTo; + public final @SnapPosition int snapPosition; public boolean isMiddleTarget; @@ -464,15 +489,15 @@ public class DividerSnapAlgorithm { */ private final float distanceMultiplier; - public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo) { - this(position, taskPosition, snapTo, 1f); + public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition) { + this(position, taskPosition, snapPosition, 1f); } - public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo, + public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition, float distanceMultiplier) { this.position = position; this.taskPosition = taskPosition; - this.snapTo = snapTo; + this.snapPosition = snapPosition; this.distanceMultiplier = distanceMultiplier; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 4af03fd5b955..26b5a5052594 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; @@ -66,6 +67,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.InteractionJankMonitorUtils; +import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.io.PrintWriter; @@ -115,7 +117,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm; private WindowContainerToken mWinToken1; private WindowContainerToken mWinToken2; - private int mDividePosition; + private int mDividerPosition; private boolean mInitialized = false; private boolean mFreezeDividerWindow = false; private int mOrientation; @@ -267,7 +269,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } int getDividePosition() { - return mDividePosition; + return mDividerPosition; + } + + /** + * Finds the {@link SnapPosition} nearest to the current divider position. + */ + public int calculateCurrentSnapPosition() { + return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition); } /** @@ -344,16 +353,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } private void initDividerPosition(Rect oldBounds) { - final float snapRatio = (float) mDividePosition + final float snapRatio = (float) mDividerPosition / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height()); // Estimate position by previous ratio. final float length = (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height()); final int estimatePosition = (int) (length * snapRatio); // Init divider position by estimated position using current bounds snap algorithm. - mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget( + mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget( estimatePosition).position; - updateBounds(mDividePosition); + updateBounds(mDividerPosition); } private void updateBounds(int position) { @@ -467,27 +476,29 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } void setDividePosition(int position, boolean applyLayoutChange) { - mDividePosition = position; - updateBounds(mDividePosition); + mDividerPosition = position; + updateBounds(mDividerPosition); if (applyLayoutChange) { mSplitLayoutHandler.onLayoutSizeChanged(this); } } /** Updates divide position and split bounds base on the ratio within root bounds. */ - public void setDivideRatio(float ratio) { - final int position = isLandscape() - ? mRootBounds.left + (int) (mRootBounds.width() * ratio) - : mRootBounds.top + (int) (mRootBounds.height() * ratio); - final DividerSnapAlgorithm.SnapTarget snapTarget = - mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); + public void setDivideRatio(@SnapPosition int snapPosition) { + final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget( + snapPosition); + + if (snapTarget == null) { + throw new IllegalArgumentException("No SnapTarget for position " + snapPosition); + } + setDividePosition(snapTarget.position, false /* applyLayoutChange */); } /** Resets divider position. */ public void resetDividerPosition() { - mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; - updateBounds(mDividePosition); + mDividerPosition = mDividerSnapAlgorithm.getMiddleTarget().position; + updateBounds(mDividerPosition); mWinToken1 = null; mWinToken2 = null; mWinBounds1.setEmpty(); @@ -510,7 +521,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * target indicates dismissing split. */ public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { - switch (snapTarget.snapTo) { + switch (snapTarget.snapPosition) { case SNAP_TO_START_AND_DISMISS: flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, @@ -668,8 +679,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @Override public void onAnimationEnd(Animator animation) { - mDividePosition = dividerPos; - updateBounds(mDividePosition); + mDividerPosition = dividerPos; + updateBounds(mDividerPosition); finishCallback.accept(insets); InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 14304a3c0aac..253acc49071a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -91,42 +91,42 @@ interface ISplitScreen { * Starts tasks simultaneously in one transition. */ oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2, - int splitPosition, float splitRatio, in RemoteTransition remoteTransition, + int splitPosition, int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 10; /** * Starts a pair of intent and task in one transition. */ oneway void startIntentAndTask(in PendingIntent pendingIntent, int userId1, in Bundle options1, - int taskId, in Bundle options2, int sidePosition, float splitRatio, + int taskId, in Bundle options2, int sidePosition, int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 16; /** * Starts a pair of shortcut and task in one transition. */ oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId, - in Bundle options2, int splitPosition, float splitRatio, + in Bundle options2, int splitPosition, int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 17; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2, - in Bundle options2, int splitPosition, float splitRatio, + in Bundle options2, int splitPosition, int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11; /** * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, int userId1, - in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, + in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; /** * Starts a pair of shortcut and task using legacy transition system. */ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, - in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, + in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15; /** @@ -135,7 +135,7 @@ interface ISplitScreen { oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1, int userId1, in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2, int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, - float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18; + int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18; /** * Start a pair of intents in one transition. @@ -143,7 +143,7 @@ interface ISplitScreen { oneway void startIntents(in PendingIntent pendingIntent1, int userId1, in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2, int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, - float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; + int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; /** * Blocking call that notifies and gets additional split-screen targets when entering diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 991b699161ea..e02ff9876c2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -86,6 +86,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -599,8 +600,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { if (options1 == null) options1 = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); @@ -624,13 +625,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo, - activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter, + activityOptions.toBundle(), taskId, options2, splitPosition, snapPosition, adapter, instanceId); } void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { if (options1 == null) options1 = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); final String packageName1 = shortcutInfo.getPackage(); @@ -657,7 +659,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId, - options2, splitPosition, splitRatio, remoteTransition, instanceId); + options2, splitPosition, snapPosition, remoteTransition, instanceId); } /** @@ -672,8 +674,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); @@ -694,12 +696,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, - options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId); + options1, taskId, options2, splitPosition, snapPosition, adapter, instanceId); } private void startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, + @SplitPosition int splitPosition, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); @@ -726,14 +728,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, - options2, splitPosition, splitRatio, remoteTransition, instanceId); + options2, splitPosition, snapPosition, remoteTransition, instanceId); } private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, - RemoteAnimationAdapter adapter, InstanceId instanceId) { + @Nullable Bundle options2, @SplitPosition int splitPosition, + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent1 = null; Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); @@ -757,14 +759,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2, - splitPosition, splitRatio, adapter, instanceId); + splitPosition, snapPosition, adapter, instanceId); } private void startIntents(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, - @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @Nullable Bundle options2, @SplitPosition int splitPosition, + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { Intent fillInIntent1 = null; Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); @@ -799,7 +802,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2, - activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition, + activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition, instanceId); } @@ -1219,78 +1222,82 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, - float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( - taskId1, options1, taskId2, options2, splitPosition, - splitRatio, adapter, instanceId)); + taskId1, options1, taskId2, options2, splitPosition, snapPosition, + adapter, instanceId)); } @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, - Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, - RemoteAnimationAdapter adapter, InstanceId instanceId) { + Bundle options1, int taskId, Bundle options2, int splitPosition, + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> controller.startIntentAndTaskWithLegacyTransition(pendingIntent, - userId1, options1, taskId, options2, splitPosition, splitRatio, - adapter, instanceId)); + userId1, options1, taskId, options2, splitPosition, + snapPosition, adapter, instanceId)); } @Override public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTaskWithLegacyTransition", (controller) -> controller.startShortcutAndTaskWithLegacyTransition( shortcutInfo, options1, taskId, options2, splitPosition, - splitRatio, adapter, instanceId)); + snapPosition, adapter, instanceId)); } @Override public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, - @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @Nullable Bundle options2, @SplitPosition int splitPosition, + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1, - taskId2, options2, splitPosition, splitRatio, remoteTransition, + taskId2, options2, splitPosition, snapPosition, remoteTransition, instanceId)); } @Override public void startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, + @SplitPosition int splitPosition, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", (controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1, - taskId, options2, splitPosition, splitRatio, remoteTransition, + taskId, options2, splitPosition, snapPosition, remoteTransition, instanceId)); } @Override public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - float splitRatio, @Nullable RemoteTransition remoteTransition, + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask", (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId, - options2, splitPosition, splitRatio, remoteTransition, instanceId)); + options2, splitPosition, snapPosition, remoteTransition, instanceId)); } @Override public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, - RemoteAnimationAdapter adapter, InstanceId instanceId) { + @Nullable Bundle options2, @SplitPosition int splitPosition, + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition", (controller) -> controller.startIntentsWithLegacyTransition(pendingIntent1, userId1, shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, - options2, splitPosition, splitRatio, adapter, instanceId) + options2, splitPosition, snapPosition, adapter, instanceId) ); } @@ -1298,13 +1305,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntents(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, - @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @Nullable Bundle options2, @SplitPosition int splitPosition, + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntents", (controller) -> controller.startIntents(pendingIntent1, userId1, shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2, - splitPosition, splitRatio, remoteTransition, instanceId) + splitPosition, snapPosition, remoteTransition, instanceId) ); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 94fa485efd5c..003273fc2e3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -128,6 +128,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.common.split.SplitWindowManager; @@ -631,8 +632,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } /** Starts 2 tasks in one transition. */ - void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, - @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, + void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, + @SplitPosition int splitPosition, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId2 == INVALID_TASK_ID) { @@ -654,13 +655,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.startTask(taskId1, options1); - startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId); + startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId); } /** Start an intent and a task to a split pair in one transition. */ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, + @SplitPosition int splitPosition, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { @@ -676,13 +677,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); - startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId); + startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } /** Starts a shortcut and a task to a split pair in one transition. */ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { options1 = options1 != null ? options1 : new Bundle(); @@ -697,7 +699,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); - startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId); + startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } /** @@ -708,14 +710,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} */ private void startWithTask(WindowContainerTransaction wct, int mainTaskId, - @Nullable Bundle mainOptions, float splitRatio, + @Nullable Bundle mainOptions, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { if (!mMainStage.isActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. mMainStage.activate(wct, false /* reparent */); } - mSplitLayout.setDivideRatio(splitRatio); + mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); @@ -740,7 +742,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, + @SplitPosition int splitPosition, @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (pendingIntent2 == null) { @@ -762,7 +764,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } setSideStagePosition(splitPosition, wct); - mSplitLayout.setDivideRatio(splitRatio); + mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); @@ -790,7 +792,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts a pair of tasks using legacy transition. */ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, - float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { @@ -811,7 +813,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.startTask(taskId1, options1); mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition); - startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter, + startWithLegacyTransition(wct, taskId2, options2, splitPosition, snapPosition, adapter, instanceId); } @@ -820,8 +822,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (pendingIntent2 == null) { @@ -840,13 +842,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition); } startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2, - splitPosition, splitRatio, adapter, instanceId); + splitPosition, snapPosition, adapter, instanceId); } void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { @@ -859,15 +861,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition); - startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter, + startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter, instanceId); } /** Starts a pair of shortcut and task using legacy transition. */ void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int splitPosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { @@ -878,7 +880,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); - startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter, + startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter, instanceId); } @@ -928,18 +930,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startWithLegacyTransition(WindowContainerTransaction wct, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, - @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int sidePosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent, - mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId); + mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId); } private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, - @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio, - RemoteAnimationAdapter adapter, InstanceId instanceId) { + @Nullable Bundle mainOptions, @SplitPosition int sidePosition, + @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */, null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition, - splitRatio, adapter, instanceId); + snapPosition, adapter, instanceId); } /** @@ -950,15 +953,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options, - @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + @SplitPosition int sidePosition, @SnapPosition int snapPosition, + RemoteAnimationAdapter adapter, InstanceId instanceId) { if (!isSplitScreenVisible()) { exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); } // Init divider first to make divider leash for remote animation target. mSplitLayout.init(); - mSplitLayout.setDivideRatio(splitRatio); + mSplitLayout.setDivideRatio(snapPosition); // Apply surface bounds before animation start. SurfaceControl.Transaction startT = mTransactionPool.acquire(); @@ -1761,7 +1764,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, rightBottomTaskId = sideStageTopTaskId; } SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, - leftTopTaskId, rightBottomTaskId); + leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition()); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java index 0edcff45f648..a68b41d6563a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java @@ -19,6 +19,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; + import java.util.Objects; /** @@ -37,6 +39,7 @@ public class SplitBounds implements Parcelable { public final float leftTaskPercent; public final float dividerWidthPercent; public final float dividerHeightPercent; + public final @SnapPosition int snapPosition; /** * If {@code true}, that means at the time of creation of this object, the * split-screened apps were vertically stacked. This is useful in scenarios like @@ -47,12 +50,13 @@ public class SplitBounds implements Parcelable { public final int leftTopTaskId; public final int rightBottomTaskId; - public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, - int leftTopTaskId, int rightBottomTaskId) { + public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, + int rightBottomTaskId, @SnapPosition int snapPosition) { this.leftTopBounds = leftTopBounds; this.rightBottomBounds = rightBottomBounds; this.leftTopTaskId = leftTopTaskId; this.rightBottomTaskId = rightBottomTaskId; + this.snapPosition = snapPosition; if (rightBottomBounds.top > leftTopBounds.top) { // vertical apps, horizontal divider @@ -83,8 +87,9 @@ public class SplitBounds implements Parcelable { appsStackedVertically = parcel.readBoolean(); leftTopTaskId = parcel.readInt(); rightBottomTaskId = parcel.readInt(); - dividerWidthPercent = parcel.readInt(); - dividerHeightPercent = parcel.readInt(); + dividerWidthPercent = parcel.readFloat(); + dividerHeightPercent = parcel.readFloat(); + snapPosition = parcel.readInt(); } @Override @@ -99,6 +104,7 @@ public class SplitBounds implements Parcelable { parcel.writeInt(rightBottomTaskId); parcel.writeFloat(dividerWidthPercent); parcel.writeFloat(dividerHeightPercent); + parcel.writeInt(snapPosition); } @Override @@ -129,7 +135,8 @@ public class SplitBounds implements Parcelable { return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" + "Divider: " + visualDividerBounds + "\n" - + "AppsVertical? " + appsStackedVertically; + + "AppsVertical? " + appsStackedVertically + "\n" + + "snapPosition: " + snapPosition; } public static final Creator CREATOR = new Creator() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index ad84c7fb6128..56d0f8e13f08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -18,9 +18,13 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -130,7 +134,7 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testSetDivideRatio() { mSplitLayout.setDividePosition(200, false /* applyLayoutChange */); - mSplitLayout.setDivideRatio(0.5f); + mSplitLayout.setDivideRatio(SNAP_TO_50_50); assertThat(mSplitLayout.getDividePosition()).isEqualTo( mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt index baa06f2f0c45..bbd65be9abda 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt @@ -24,6 +24,7 @@ import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50 import com.android.wm.shell.util.GroupedRecentTaskInfo import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM @@ -123,6 +124,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2) assertThat(recentTaskInfoParcel.splitBounds).isNotNull() + assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50) } @Test @@ -156,7 +158,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) - val splitBounds = SplitBounds(Rect(), Rect(), 1, 2) + val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50) return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 40ce7859cc7f..10e9e11e9004 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -173,10 +174,10 @@ public class RecentTasksControllerTest extends ShellTestCase { // Verify only one update if the split info is the same SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50), - new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50), - new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); } @@ -207,8 +208,10 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4); - SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5); + SplitBounds pair1Bounds = + new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50); + SplitBounds pair2Bounds = + new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50); mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); @@ -236,8 +239,10 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4); - SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5); + SplitBounds pair1Bounds = + new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50); + SplitBounds pair2Bounds = + new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50); mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); @@ -334,7 +339,8 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3); // Add a pair - SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3); + SplitBounds pair1Bounds = + new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50); mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java index 50d02ae0dccd..b790aee6fb0e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java @@ -1,5 +1,7 @@ package com.android.wm.shell.recents; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -44,21 +46,21 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testVerticalStacked() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); assertTrue(ssb.appsStackedVertically); } @Test public void testHorizontalStacked() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); assertFalse(ssb.appsStackedVertically); } @Test public void testHorizontalDividerBounds() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(0, dividerBounds.left); assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); @@ -69,7 +71,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testVerticalDividerBounds() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); assertEquals(0, dividerBounds.top); @@ -80,7 +82,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testEqualVerticalTaskPercent() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); } @@ -88,7 +90,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testEqualHorizontalTaskPercent() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2); + TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); } -- GitLab From 4added4d55720e70d8d8eab6489dc61e0c0b5e6b Mon Sep 17 00:00:00 2001 From: Jakob Schneider Date: Fri, 15 Sep 2023 17:16:46 +0100 Subject: [PATCH 13/95] Check for isInstalled when checking if an app is actually archived. Bug: 290776158 Test: PackageInstallArchiveTest Change-Id: I1110e4608f27af39e0b7aed5535165b123a611f9 --- .../com/android/server/pm/ComputerEngine.java | 11 +++-------- .../com/android/server/pm/PackageArchiver.java | 9 +++++++-- .../server/pm/parsing/PackageInfoUtils.java | 10 ++-------- .../android/server/pm/PackageArchiverTest.java | 18 ++++++++++++++++-- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 316c4aca1891..6baa889d61ae 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1700,7 +1700,9 @@ public class ComputerEngine implements Computer { if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) { continue; } - if (listArchivedOnly && !isArchived(ps.getUserStateOrDefault(userId))) { + PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); + if (listArchivedOnly && !userState.isInstalled() + && userState.getArchiveState() == null) { continue; } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { @@ -1746,13 +1748,6 @@ public class ComputerEngine implements Computer { return new ParceledListSlice<>(list); } - // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed. - // If an app has isInstalled() == true - it should not be filtered above in any case, currently - // it is. - private static boolean isArchived(PackageUserStateInternal userState) { - return userState.getArchiveState() != null; - } - public final ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, int sourceUserId, int targetUserId) { ResolveInfo forwardingResolveInfo = new ResolveInfo(); diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 803b6e45cdca..f59188e9fd93 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -58,6 +58,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import java.io.ByteArrayOutputStream; @@ -101,6 +102,11 @@ public class PackageArchiver { this.mPm = mPm; } + /** Returns whether a package is archived for a user. */ + public static boolean isArchived(PackageUserState userState) { + return userState.getArchiveState() != null && !userState.isInstalled(); + } + void requestArchive( @NonNull String packageName, @NonNull String callerPackageName, @@ -354,8 +360,7 @@ public class PackageArchiver { private void verifyArchived(PackageStateInternal ps, int userId) throws PackageManager.NameNotFoundException { PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); - // TODO(b/288142708) Check for isInstalled false here too. - if (userState.getArchiveState() == null) { + if (!isArchived(userState)) { throw new PackageManager.NameNotFoundException( TextUtils.formatSimple("Package %s is not currently archived.", ps.getPackageName())); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index cc8e62409597..d16a81267370 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -54,6 +54,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; +import com.android.server.pm.PackageArchiver; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; @@ -401,14 +402,7 @@ public class PackageInfoUtils { ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); } - ai.isArchived = isArchived(state); - } - - // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed. - // If an app has isInstalled() == true - it should not be filtered above in any case, currently - // it is. - private static boolean isArchived(PackageUserState userState) { - return userState.getArchiveState() != null; + ai.isArchived = PackageArchiver.isArchived(state); } @Nullable diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index b97fd4b3277b..eb50556821eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -315,7 +315,8 @@ public class PackageArchiverTest { } @Test - public void unarchiveApp_notArchived() { + public void unarchiveApp_notArchived_missingArchiveState() { + mUserState.setInstalled(false); Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, @@ -326,8 +327,21 @@ public class PackageArchiverTest { } @Test - public void unarchiveApp_noInstallerFound() { + public void unarchiveApp_notArchived_stillInstalled() { mUserState.setArchiveState(createArchiveState()); + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s is not currently archived.", PACKAGE)); + } + + + @Test + public void unarchiveApp_noInstallerFound() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); InstallSource otherInstallSource = InstallSource.create( CALLER_PACKAGE, -- GitLab From 88301fedcbff39956a0226d0f6d6e57b10f77a6f Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Tue, 19 Sep 2023 18:22:44 +0000 Subject: [PATCH 14/95] Revert "Add temporary logging to root cause an issue." This reverts commit 1cdfa801f5911c76b3b8a88f7e34a9eaf30840d5. Reason for revert: not needed any more Change-Id: I2dfca6cbe9e79cbfcde5b4e6f77ce483b5d93642 --- .../com/android/server/am/ActivityManagerService.java | 11 ----------- .../android/server/am/BroadcastQueueModernImpl.java | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1fa60fef401b..967177219475 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -314,7 +314,6 @@ import android.net.ConnectivityManager; import android.net.Proxy; import android.net.Uri; import android.os.AppZygote; -import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.BinderProxy; @@ -15030,16 +15029,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - // STOPSHIP(b/298884211): Remove this logging - if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { - final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - if (level < 0) { - Slog.wtf(BroadcastQueue.TAG, "Unexpected broadcast: " + intent - + "; callingUid: " + callingUid + ", callingPid: " + callingPid, - new Throwable()); - } - } - int[] users; if (userId == UserHandle.USER_ALL) { // Caller wants broadcast to go to all started users. diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d5343a9777a7..5d0fefc75a28 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -59,7 +59,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.BatteryManager; import android.os.Bundle; import android.os.BundleMerger; import android.os.Handler; @@ -1075,16 +1074,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.lastProcessState = app.mState.getCurProcState(); if (receiver instanceof BroadcastFilter) { notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); - // STOPSHIP(b/298884211): Remove this logging - if (Intent.ACTION_BATTERY_CHANGED.equals(receiverIntent.getAction())) { - int level = receiverIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - if (level < 0) { - Slog.wtf(TAG, "Dispatching unexpected broadcast: " + receiverIntent - + " to " + receiver - + "; callingUid: " + r.callingUid - + ", callingPid: " + r.callingPid); - } - } thread.scheduleRegisteredReceiver( ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent, r.resultCode, r.resultData, r.resultExtras, -- GitLab From 2fd7a0b3be945aa7b21f84da2592204dd7025b2d Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Tue, 22 Aug 2023 17:08:59 -0400 Subject: [PATCH 15/95] [StatusBar] Request layout on vis change, and move to CREATED The flow that was being bound to the mobile icon group's visibility lived in STARTED (this caused tests to pass). It also was experiencing a race condition against the StatusIconContainer. In certain circumstances, the view was being created and sent to have an icon state of HIDDEN, despite having changed itself to be VISIBLE. This is all emergent behavior from StatusIconContainer, which decides which state to set on icons in its onMeasure(), but then applies that state in onLayout(). To test: remove battery percent from status bar; ensure that _no other icon_ is showing. This means disable Wi-Fi and make sure volume is set to vibrate or on, since those states have no icon. Once you are in a state where there is only battery and mobile showing, remove and re-insert the SIM card to reproduce. Test: ModernStatusBarMobileViewTest Test: manually removing and inserting SIM card Fixes: 291031862 Change-Id: I0e68037126a526fcf3614bf25683af3fa4e5aa96 Merged-In: I0e68037126a526fcf3614bf25683af3fa4e5aa96 --- .../mobile/ui/binder/MobileIconBinder.kt | 165 ++++++++++-------- .../ShadeCarrierGroupControllerTest.java | 12 +- .../systemui/util/kotlin/FlowProvider.kt | 22 +++ 3 files changed, 122 insertions(+), 77 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 6483b7c647e8..7ec8e12e557e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -25,6 +25,7 @@ import android.widget.ImageView import android.widget.Space import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.graph.SignalDrawable import com.android.systemui.R @@ -62,7 +63,7 @@ object MobileIconBinder { val roamingSpace = view.requireViewById(R.id.mobile_roaming_space) val dotView = view.requireViewById(R.id.status_bar_dot) - view.isVisible = true + view.isVisible = viewModel.isVisible.value iconView.isVisible = true // TODO(b/238425913): We should log this visibility state. @@ -75,99 +76,113 @@ object MobileIconBinder { var isCollecting = false view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - logger.logCollectionStarted(view, viewModel) - isCollecting = true - - launch { - visibilityState.collect { state -> - ModernStatusBarViewVisibilityHelper.setVisibilityState( - state, - mobileGroupView, - dotView, - ) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + // isVisible controls the visibility state of the outer group, and thus it needs + // to run in the CREATED lifecycle so it can continue to watch while invisible + // See (b/291031862) for details + launch { + viewModel.isVisible.collect { isVisible -> + viewModel.verboseLogger?.logBinderReceivedVisibility( + view, + viewModel.subscriptionId, + isVisible + ) + view.isVisible = isVisible + // [StatusIconContainer] can get out of sync sometimes. Make sure to + // request another layout when this changes. + view.requestLayout() + } } } + } - launch { - viewModel.isVisible.collect { isVisible -> - viewModel.verboseLogger?.logBinderReceivedVisibility( - view, - viewModel.subscriptionId, - isVisible - ) - view.isVisible = isVisible + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + logger.logCollectionStarted(view, viewModel) + isCollecting = true + + launch { + visibilityState.collect { state -> + ModernStatusBarViewVisibilityHelper.setVisibilityState( + state, + mobileGroupView, + dotView, + ) + } } - } - // Set the icon for the triangle - launch { - viewModel.icon.distinctUntilChanged().collect { icon -> - viewModel.verboseLogger?.logBinderReceivedSignalIcon( - view, - viewModel.subscriptionId, - icon, - ) - mobileDrawable.level = icon.toSignalDrawableState() + // Set the icon for the triangle + launch { + viewModel.icon.distinctUntilChanged().collect { icon -> + viewModel.verboseLogger?.logBinderReceivedSignalIcon( + view, + viewModel.subscriptionId, + icon, + ) + mobileDrawable.level = icon.toSignalDrawableState() + } } - } - launch { - viewModel.contentDescription.distinctUntilChanged().collect { - ContentDescriptionViewBinder.bind(it, view) + launch { + viewModel.contentDescription.distinctUntilChanged().collect { + ContentDescriptionViewBinder.bind(it, view) + } } - } - // Set the network type icon - launch { - viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> - viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon( - view, - viewModel.subscriptionId, - dataTypeId, - ) - dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } - networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE + // Set the network type icon + launch { + viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> + viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon( + view, + viewModel.subscriptionId, + dataTypeId, + ) + dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } + networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE + } } - } - // Set the roaming indicator - launch { - viewModel.roaming.distinctUntilChanged().collect { isRoaming -> - roamingView.isVisible = isRoaming - roamingSpace.isVisible = isRoaming + // Set the roaming indicator + launch { + viewModel.roaming.distinctUntilChanged().collect { isRoaming -> + roamingView.isVisible = isRoaming + roamingSpace.isVisible = isRoaming + } } - } - // Set the activity indicators - launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } } + // Set the activity indicators + launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } } - launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } } + launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } } - launch { - viewModel.activityContainerVisible.collect { activityContainer.isVisible = it } - } + launch { + viewModel.activityContainerVisible.collect { + activityContainer.isVisible = it + } + } - // Set the tint - launch { - iconTint.collect { tint -> - val tintList = ColorStateList.valueOf(tint) - iconView.imageTintList = tintList - networkTypeView.imageTintList = tintList - roamingView.imageTintList = tintList - activityIn.imageTintList = tintList - activityOut.imageTintList = tintList - dotView.setDecorColor(tint) + // Set the tint + launch { + iconTint.collect { tint -> + val tintList = ColorStateList.valueOf(tint) + iconView.imageTintList = tintList + networkTypeView.imageTintList = tintList + roamingView.imageTintList = tintList + activityIn.imageTintList = tintList + activityOut.imageTintList = tintList + dotView.setDecorColor(tint) + } } - } - launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } + launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } - try { - awaitCancellation() - } finally { - isCollecting = false - logger.logCollectionStopped(view, viewModel) + try { + awaitCancellation() + } finally { + isCollecting = false + logger.logCollectionStopped(view, viewModel) + } } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java index 5fa6b3a15d35..e7056c7b0b9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel; import com.android.systemui.util.CarrierConfigTracker; +import com.android.systemui.util.kotlin.FlowProviderKt; import com.android.systemui.utils.leaks.LeakCheckedTest; import com.android.systemui.utils.os.FakeHandler; @@ -72,6 +73,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import kotlinx.coroutines.flow.MutableStateFlow; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -105,7 +108,8 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { private ShadeCarrier mShadeCarrier3; private TestableLooper mTestableLooper; @Mock - private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener; + private ShadeCarrierGroupController.OnSingleCarrierChangedListener + mOnSingleCarrierChangedListener; @Mock private MobileUiAdapter mMobileUiAdapter; @Mock @@ -119,6 +123,9 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { @Mock private StatusBarPipelineFlags mStatusBarPipelineFlags; + private final MutableStateFlow mIsVisibleFlow = + FlowProviderKt.getMutableStateFlow(true); + private FakeSlotIndexResolver mSlotIndexResolver; private ClickListenerTextView mNoCarrierTextView; @@ -170,7 +177,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { mMobileUiAdapter, mMobileContextProvider, mStatusBarPipelineFlags - ) + ) .setShadeCarrierGroup(mShadeCarrierGroup) .build(); @@ -181,6 +188,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { when(mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()).thenReturn(true); when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext); when(mMobileIconsViewModel.getLogger()).thenReturn(mMobileViewLogger); + when(mShadeCarrierGroupMobileIconViewModel.isVisible()).thenReturn(mIsVisibleFlow); when(mMobileIconsViewModel.viewModelForSub(anyInt(), any())) .thenReturn(mShadeCarrierGroupMobileIconViewModel); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt new file mode 100644 index 000000000000..274acc9a8c23 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.util.kotlin + +import kotlinx.coroutines.flow.MutableStateFlow + +/** Wrapper for flow constructors that can be retrieved from java tests */ +fun getMutableStateFlow(value: T): MutableStateFlow = MutableStateFlow(value) -- GitLab From 16ecfea7e5009f3b02c6121ba37dee2c7bcac698 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Tue, 19 Sep 2023 15:17:21 -0700 Subject: [PATCH 16/95] Synchronize on ArrayList object mutations to avoid AIOOBE mUnfinishedRequests is being modified in two different threads, which is unsafe for an ArrayList. Synchronize the mutation calls. Fixes: 294930461 Test: Manual flash and use device, as crash repro case is not available Change-Id: I344d5504409cbc4d86ee52528576e965e117979c --- .../android/internal/infra/AbstractRemoteService.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index 18414cf93bc8..556c246e16d6 100644 --- a/core/java/com/android/internal/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -355,9 +355,10 @@ public abstract class AbstractRemoteService finshedRequest) { - mUnfinishedRequests.remove(finshedRequest); - + private void handleFinishRequest(@NonNull BasePendingRequest finishedRequest) { + synchronized (mUnfinishedRequests) { + mUnfinishedRequests.remove(finishedRequest); + } if (mUnfinishedRequests.isEmpty()) { scheduleUnbind(); } @@ -460,7 +461,9 @@ public abstract class AbstractRemoteService Date: Wed, 6 Sep 2023 07:31:32 +0000 Subject: [PATCH 17/95] Add new apis for satellite service Added an api to find whether the subscription ID supports NTN network. Added device overlay configuration for telephony to identify the eSIM profile preloaded on a device supports satellite service. Bug: 299232193 Test: atest SubscriptionInfoTest, SubscriptionInfoInternalTest Change-Id: I41ec533df142ee0c4d2bba524acddf90a16509d4 --- core/api/current.txt | 1 + core/java/android/provider/Telephony.java | 11 ++- core/res/res/values/config_telephony.xml | 5 ++ .../android/telephony/SubscriptionInfo.java | 46 +++++++++- .../telephony/SubscriptionManager.java | 8 ++ .../com/android/internal/telephony/ISub.aidl | 90 +++++++++---------- 6 files changed, 113 insertions(+), 48 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index f488c82a2a60..c39c61654e61 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -44887,6 +44887,7 @@ package android.telephony { method public int getSubscriptionType(); method public int getUsageSetting(); method public boolean isEmbedded(); + method @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isNtn(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index cf3707b8e105..a39157103f71 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4915,6 +4915,14 @@ public final class Telephony { public static final String COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER = "satellite_attach_enabled_for_carrier"; + /** + * TelephonyProvider column name to identify eSIM profile of a non-terrestrial network. + * By default, it's disabled. + * + * @hide + */ + public static final String COLUMN_IS_NTN = "is_ntn"; + /** All columns in {@link SimInfo} table. */ private static final List ALL_COLUMNS = List.of( COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID, @@ -4985,7 +4993,8 @@ public final class Telephony { COLUMN_TP_MESSAGE_REF, COLUMN_USER_HANDLE, COLUMN_SATELLITE_ENABLED, - COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER + COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER, + COLUMN_IS_NTN ); /** diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 582153766dbe..77d0d6dc376f 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -190,6 +190,11 @@ + + + + + + + diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt new file mode 100644 index 000000000000..6627af526dee --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt @@ -0,0 +1,42 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui + +import android.content.Intent +import android.credentials.ui.RequestInfo +import com.android.credentialmanager.ui.ktx.cancelUiRequest +import com.android.credentialmanager.ui.ktx.requestInfo +import com.android.credentialmanager.ui.mapper.toCancel +import com.android.credentialmanager.ui.model.Request + +fun Intent.parse(): Request { + cancelUiRequest?.let { + return it.toCancel() + } + + return when (requestInfo?.type) { + RequestInfo.TYPE_CREATE -> { + Request.Create + } + RequestInfo.TYPE_GET -> { + Request.Get + } + else -> { + throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") + } + } +} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt new file mode 100644 index 000000000000..f49bb33d2e8a --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt @@ -0,0 +1,19 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui + +const val TAG = "CredentialSelector" diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt new file mode 100644 index 000000000000..a646851bf570 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt @@ -0,0 +1,33 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui.ktx + +import android.content.Intent +import android.credentials.ui.CancelUiRequest +import android.credentials.ui.RequestInfo + +val Intent.cancelUiRequest: CancelUiRequest? + get() = this.extras?.getParcelable( + CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, + CancelUiRequest::class.java + ) + +val Intent.requestInfo: RequestInfo? + get() = this.extras?.getParcelable( + RequestInfo.EXTRA_REQUEST_INFO, + RequestInfo::class.java + ) \ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.kt new file mode 100644 index 000000000000..7fa0ca918e21 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.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.0N + * + * 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.credentialmanager.ui.ktx + +import android.content.pm.PackageManager +import android.text.TextUtils +import android.util.Log +import com.android.credentialmanager.ui.TAG + +fun PackageManager.appLabel(appPackageName: String): String? = + try { + val pkgInfo = this.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0)) + val applicationInfo = checkNotNull(pkgInfo.applicationInfo) + applicationInfo.loadSafeLabel( + this, 0f, + TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM + ).toString() + } catch (e: Exception) { + Log.e(TAG, "Caller app not found", e) + null + } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt new file mode 100644 index 000000000000..89766c2ec61b --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * 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.credentialmanager.ui.mapper + +import android.credentials.ui.CancelUiRequest +import com.android.credentialmanager.ui.model.Request + +fun CancelUiRequest.toCancel() = Request.Cancel( + showCancellationUi = this.shouldShowCancellationUi(), + appPackageName = this.appPackageName +) diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt new file mode 100644 index 000000000000..3d835bebc06b --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt @@ -0,0 +1,31 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui.model + +/** + * Represents the request made by the CredentialManager API. + */ +sealed class Request { + data class Cancel( + val showCancellationUi: Boolean, + val appPackageName: String? + ) : Request() + + data object Get : Request() + + data object Create : Request() +} diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp index 639e8d18b306..36340fac1760 100644 --- a/packages/CredentialManager/wear/Android.bp +++ b/packages/CredentialManager/wear/Android.bp @@ -21,6 +21,7 @@ android_app { }, static_libs: [ + "CredentialManagerShared", "Horologist", "PlatformComposeCore", "androidx.activity_activity-compose", diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt index 77fffaa1e04c..2c0575547162 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt @@ -16,28 +16,73 @@ package com.android.credentialmanager.ui +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.navigation.NavHostController +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.wear.compose.material.MaterialTheme -import androidx.wear.compose.navigation.rememberSwipeDismissableNavController +import kotlinx.coroutines.launch class CredentialSelectorActivity : ComponentActivity() { - lateinit var navController: NavHostController + private val viewModel: CredentialSelectorViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(android.R.style.Theme_DeviceDefault) - setContent { - navController = rememberSwipeDismissableNavController() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { uiState -> + when (uiState) { + CredentialSelectorUiState.Idle -> { + // Don't display anything, assuming that there should be minimal latency + // to parse the Credential Manager intent and define the state of the + // app. If latency is big, then a "loading" screen should be displayed + // to the user. + } - MaterialTheme { - WearApp(navController = navController) + CredentialSelectorUiState.Get -> { + // TODO: b/301206470 - Implement get flow + setContent { + MaterialTheme { + WearApp() + } + } + } + + CredentialSelectorUiState.Create -> { + // TODO: b/301206624 - Implement create flow + finish() + } + + is CredentialSelectorUiState.Cancel -> { + // TODO: b/300422310 - Implement cancel with message flow + finish() + } + + CredentialSelectorUiState.Finish -> { + finish() + } + } + } } } + + viewModel.onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + val previousIntent = getIntent() + setIntent(intent) + + viewModel.onNewIntent(intent, previousIntent) } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt new file mode 100644 index 000000000000..e46fcae78f6a --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt @@ -0,0 +1,111 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui + +import android.app.Application +import android.content.Intent +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.android.credentialmanager.ui.ktx.appLabel +import com.android.credentialmanager.ui.ktx.requestInfo +import com.android.credentialmanager.ui.model.Request +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class CredentialSelectorViewModel( + private val application: Application +) : AndroidViewModel(application = application) { + + private val _uiState = + MutableStateFlow(CredentialSelectorUiState.Idle) + val uiState: StateFlow = _uiState + + fun onNewIntent(intent: Intent, previousIntent: Intent? = null) { + viewModelScope.launch { + val request = intent.parse() + if (shouldFinishActivity(request = request, previousIntent = previousIntent)) { + _uiState.value = CredentialSelectorUiState.Finish + } else { + when (request) { + is Request.Cancel -> { + request.appPackageName?.let { appPackageName -> + application.packageManager.appLabel(appPackageName)?.let { appLabel -> + _uiState.value = CredentialSelectorUiState.Cancel(appLabel) + } ?: run { + Log.d(TAG, + "Received UI cancel request with an invalid package name.") + _uiState.value = CredentialSelectorUiState.Finish + } + } ?: run { + Log.d(TAG, "Received UI cancel request with an invalid package name.") + _uiState.value = CredentialSelectorUiState.Finish + } + } + + Request.Create -> { + _uiState.value = CredentialSelectorUiState.Create + } + + Request.Get -> { + _uiState.value = CredentialSelectorUiState.Get + } + } + } + } + } + + /** + * Check if backend requested the UI activity to be cancelled. Different from the other + * finishing flows, this one does not report anything back to the Credential Manager service + * backend. + */ + private fun shouldFinishActivity(request: Request, previousIntent: Intent? = null): Boolean { + if (request !is Request.Cancel) { + return false + } else { + Log.d( + TAG, "Received UI cancellation intent. Should show cancellation" + + " ui = ${request.showCancellationUi}") + + previousIntent?.let { + val previousUiRequest = previousIntent.parse() + + if (previousUiRequest is Request.Cancel) { + val previousToken = previousIntent.requestInfo?.token + val currentToken = previousIntent.requestInfo?.token + + if (previousToken != currentToken) { + // Cancellation was for a different request, don't cancel the current UI. + return false + } + } + } + + return !request.showCancellationUi + } + } +} + +sealed class CredentialSelectorUiState { + object Idle : CredentialSelectorUiState() + object Get : CredentialSelectorUiState() + object Create : CredentialSelectorUiState() + data class Cancel(val appName: String) : CredentialSelectorUiState() + object Finish : CredentialSelectorUiState() +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt index 5ec0c8cd9292..19ea9ede9d98 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt @@ -19,8 +19,8 @@ package com.android.credentialmanager.ui import androidx.compose.runtime.Composable -import androidx.navigation.NavHostController import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState +import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState import com.android.credentialmanager.ui.screens.MainScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi @@ -28,9 +28,8 @@ import com.google.android.horologist.compose.navscaffold.WearNavScaffold import com.google.android.horologist.compose.navscaffold.composable @Composable -fun WearApp( - navController: NavHostController -) { +fun WearApp() { + val navController = rememberSwipeDismissableNavController() val swipeToDismissBoxState = rememberSwipeToDismissBoxState() val navHostState = rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState) -- GitLab From 05d5d5c2bc520ff20b6a22bf2e8dbfb9451ffad6 Mon Sep 17 00:00:00 2001 From: beatricemarch Date: Wed, 23 Aug 2023 15:29:10 +0000 Subject: [PATCH 19/95] Overload putMonitoringExtra so that it can take an int as a parameter. This will allow to correcly store EXTRA_LOG_OPERATION_TYPE Test: atest BackupManagerMonitorEventSender, atest CtsBackupHostTestCases, GtsBackupHostTestCases manual testing (restore to a phone and check that the backup dumpsys contain non-agent events) Bug: 297163190 Change-Id: I6bea76f31dba66c3ad958212d77be7e00902bb2b --- .../BackupManagerMonitorEventSender.java | 17 ++++++++++++ .../BackupManagerMonitorEventSenderTest.java | 27 +++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java index 92e3107b6977..549d08c03933 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java @@ -222,4 +222,21 @@ public class BackupManagerMonitorEventSender { extras.putBoolean(key, value); return extras; } + + /** + * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will + * be created. + * + * @param extras - bundle where to add key-value to, if null a new bundle will be created. + * @param key - key. + * @param value - value. + * @return extras if it was not null and new bundle otherwise. + */ + public static Bundle putMonitoringExtra(Bundle extras, String key, int value) { + if (extras == null) { + extras = new Bundle(); + } + extras.putInt(key, value); + return extras; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java index 3af2932ee937..604a68d12f5e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java @@ -340,8 +340,9 @@ public class BackupManagerMonitorEventSenderTest { @Test public void putMonitoringExtraLong_bundleExists_fillsBundleCorrectly() throws Exception { Bundle bundle = new Bundle(); + long value = 123; - Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", 123); + Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", value); assertThat(result).isEqualTo(bundle); assertThat(result.size()).isEqualTo(1); @@ -350,7 +351,8 @@ public class BackupManagerMonitorEventSenderTest { @Test public void putMonitoringExtraLong_bundleDoesNotExist_fillsBundleCorrectly() throws Exception { - Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", 123); + long value = 123; + Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", value); assertThat(result).isNotNull(); assertThat(result.size()).isEqualTo(1); @@ -377,4 +379,25 @@ public class BackupManagerMonitorEventSenderTest { assertThat(result.size()).isEqualTo(1); assertThat(result.getBoolean("key")).isTrue(); } + + @Test + public void putMonitoringExtraInt_bundleExists_fillsBundleCorrectly() throws Exception { + Bundle bundle = new Bundle(); + + Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", 1); + + assertThat(result).isEqualTo(bundle); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getInt("key")).isEqualTo(1); + } + + @Test + public void putMonitoringExtraInt_bundleDoesNotExist_fillsBundleCorrectly() + throws Exception { + Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", 1); + + assertThat(result).isNotNull(); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getInt("key")).isEqualTo(1); + } } -- GitLab From 1b649822d6203dabf2422eca9c035ce23a3e6792 Mon Sep 17 00:00:00 2001 From: Eino-Ville Talvala Date: Thu, 14 Sep 2023 17:18:37 -0700 Subject: [PATCH 20/95] Camera: Add metrics for ultrawide-angle usage Test: Verify locally that ultrawide usage is detected correctly Bug: 300515796 Change-Id: I372a8eda235b7ddb3d146e84298b515f4ef06553 --- .../android/hardware/CameraSessionStats.java | 12 +++++++++++ .../server/camera/CameraServiceProxy.java | 21 ++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java index 1c2cbbc81971..b1d6ac4b4cd2 100644 --- a/core/java/android/hardware/CameraSessionStats.java +++ b/core/java/android/hardware/CameraSessionStats.java @@ -64,6 +64,7 @@ public class CameraSessionStats implements Parcelable { private ArrayList mStreamStats; private String mUserTag; private int mVideoStabilizationMode; + private boolean mUsedUltraWide; private int mSessionIndex; private CameraExtensionSessionStats mCameraExtensionSessionStats; @@ -82,6 +83,7 @@ public class CameraSessionStats implements Parcelable { mDeviceError = false; mStreamStats = new ArrayList(); mVideoStabilizationMode = -1; + mUsedUltraWide = false; mSessionIndex = 0; mCameraExtensionSessionStats = new CameraExtensionSessionStats(); } @@ -102,6 +104,8 @@ public class CameraSessionStats implements Parcelable { mSessionType = sessionType; mInternalReconfigure = internalReconfigure; mStreamStats = new ArrayList(); + mVideoStabilizationMode = -1; + mUsedUltraWide = false; mSessionIndex = sessionIdx; mCameraExtensionSessionStats = new CameraExtensionSessionStats(); } @@ -147,6 +151,7 @@ public class CameraSessionStats implements Parcelable { dest.writeTypedList(mStreamStats); dest.writeString(mUserTag); dest.writeInt(mVideoStabilizationMode); + dest.writeBoolean(mUsedUltraWide); dest.writeInt(mSessionIndex); mCameraExtensionSessionStats.writeToParcel(dest, 0); } @@ -173,6 +178,9 @@ public class CameraSessionStats implements Parcelable { mUserTag = in.readString(); mVideoStabilizationMode = in.readInt(); + + mUsedUltraWide = in.readBoolean(); + mSessionIndex = in.readInt(); mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in); } @@ -245,6 +253,10 @@ public class CameraSessionStats implements Parcelable { return mVideoStabilizationMode; } + public boolean getUsedUltraWide() { + return mUsedUltraWide; + } + public int getSessionIndex() { return mSessionIndex; } diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index df16c5bcc673..4ef2f1e98a80 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -77,6 +77,7 @@ import android.view.WindowManagerGlobal; import com.android.framework.protobuf.nano.MessageNano; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -244,6 +245,7 @@ public class CameraServiceProxy extends SystemService public List mStreamStats; public String mUserTag; public int mVideoStabilizationMode; + public boolean mUsedUltraWide; public final long mLogId; public final int mSessionIndex; @@ -271,7 +273,8 @@ public class CameraServiceProxy extends SystemService public void markCompleted(int internalReconfigure, long requestCount, long resultErrorCount, boolean deviceError, List streamStats, String userTag, - int videoStabilizationMode, CameraExtensionSessionStats extStats) { + int videoStabilizationMode, boolean usedUltraWide, + CameraExtensionSessionStats extStats) { if (mCompleted) { return; } @@ -284,6 +287,7 @@ public class CameraServiceProxy extends SystemService mStreamStats = streamStats; mUserTag = userTag; mVideoStabilizationMode = videoStabilizationMode; + mUsedUltraWide = usedUltraWide; mExtSessionStats = extStats; if (CameraServiceProxy.DEBUG) { Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) + @@ -873,6 +877,10 @@ public class CameraServiceProxy extends SystemService streamCount = e.mStreamStats.size(); } if (CameraServiceProxy.DEBUG) { + String ultrawideDebug = Flags.logUltrawideUsage() + ? ", wideAngleUsage " + e.mUsedUltraWide + : ""; + Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction + " clientName " + e.mClientName + ", duration " + e.getDuration() @@ -889,6 +897,7 @@ public class CameraServiceProxy extends SystemService + ", streamCount is " + streamCount + ", userTag is " + e.mUserTag + ", videoStabilizationMode " + e.mVideoStabilizationMode + + ultrawideDebug + ", logId " + e.mLogId + ", sessionIndex " + e.mSessionIndex + ", mExtSessionStats {type " + extensionType @@ -952,8 +961,9 @@ public class CameraServiceProxy extends SystemService MessageNano.toByteArray(streamProtos[2]), MessageNano.toByteArray(streamProtos[3]), MessageNano.toByteArray(streamProtos[4]), - e.mUserTag, e.mVideoStabilizationMode, e.mLogId, e.mSessionIndex, - extensionType, extensionIsAdvanced); + e.mUserTag, e.mVideoStabilizationMode, + e.mLogId, e.mSessionIndex, + extensionType, extensionIsAdvanced, e.mUsedUltraWide); } } @@ -1148,6 +1158,7 @@ public class CameraServiceProxy extends SystemService List streamStats = cameraState.getStreamStats(); String userTag = cameraState.getUserTag(); int videoStabilizationMode = cameraState.getVideoStabilizationMode(); + boolean usedUltraWide = Flags.logUltrawideUsage() ? cameraState.getUsedUltraWide() : false; long logId = cameraState.getLogId(); int sessionIdx = cameraState.getSessionIndex(); CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats(); @@ -1205,7 +1216,7 @@ public class CameraServiceProxy extends SystemService Slog.w(TAG, "Camera " + cameraId + " was already marked as active"); oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0, /*resultErrorCount*/0, /*deviceError*/false, streamStats, - /*userTag*/"", /*videoStabilizationMode*/-1, + /*userTag*/"", /*videoStabilizationMode*/-1, /*usedUltraWide*/false, new CameraExtensionSessionStats()); mCameraUsageHistory.add(oldEvent); } @@ -1217,7 +1228,7 @@ public class CameraServiceProxy extends SystemService doneEvent.markCompleted(internalReconfigureCount, requestCount, resultErrorCount, deviceError, streamStats, userTag, - videoStabilizationMode, extSessionStats); + videoStabilizationMode, usedUltraWide, extSessionStats); mCameraUsageHistory.add(doneEvent); // Do not double count device error deviceError = false; -- GitLab From 679b34cd05cf2e4465d7f7fb05f317959e8891e2 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Tue, 5 Sep 2023 21:26:10 +0000 Subject: [PATCH 21/95] Fix several flaky DeviceStateManagerServiceTests Fixes: 297949293 Bug: 223153452 Test: run each test with 100 iterations Change-Id: I333dcc2c10346ce585d1c5e7f16cad113b2b5a95 Merged-In: I333dcc2c10346ce585d1c5e7f16cad113b2b5a95 --- .../DeviceStateManagerServiceTest.java | 99 +++++++++---------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 94f88abf7301..567410145831 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,8 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static com.android.compatibility.common.util.PollingCheck.waitFor; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -40,6 +42,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; +import com.android.compatibility.common.util.PollingCheck; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowProcessController; @@ -73,6 +76,8 @@ public final class DeviceStateManagerServiceTest { private static final int FAKE_PROCESS_ID = 100; + private static final int TIMEOUT = 2000; + private TestDeviceStatePolicy mPolicy; private TestDeviceStateProvider mProvider; private DeviceStateManagerService mService; @@ -106,6 +111,10 @@ public final class DeviceStateManagerServiceTest { } } + private void waitAndAssert(PollingCheck.PollingCheckCondition condition) { + waitFor(TIMEOUT, condition); + } + @Test public void baseStateChanged() { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -272,37 +281,34 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().registerCallback(callback); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); - flushHandler(); - assertEquals(callback.getLastNotifiedInfo().baseState, - OTHER_DEVICE_STATE.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().currentState, - OTHER_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == OTHER_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().currentState + == OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); - flushHandler(); - assertEquals(callback.getLastNotifiedInfo().baseState, - DEFAULT_DEVICE_STATE.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().currentState, - DEFAULT_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == DEFAULT_DEVICE_STATE.getIdentifier()); + + waitAndAssert(() -> callback.getLastNotifiedInfo().currentState + == DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.blockConfigure(); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); - flushHandler(); // The callback should not have been notified of the state change as the policy is still // pending callback. - assertEquals(callback.getLastNotifiedInfo().baseState, - DEFAULT_DEVICE_STATE.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().currentState, - DEFAULT_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == DEFAULT_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().currentState + == DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); - flushHandler(); // Now that the policy is finished processing the callback should be notified of the state // change. - assertEquals(callback.getLastNotifiedInfo().baseState, - OTHER_DEVICE_STATE.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().currentState, - OTHER_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == OTHER_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().currentState + == OTHER_DEVICE_STATE.getIdentifier()); } @Test @@ -329,13 +335,9 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); - // Flush the handler twice. The first flush ensures the request is added and the policy is - // notified, while the second flush ensures the callback is notified once the change is - // committed. - flushHandler(2 /* count */); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_ACTIVE); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_ACTIVE); // Committed state changes as there is a requested override. assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mSysPropSetter.getValue(), @@ -352,12 +354,11 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mService.getBinderService().cancelStateRequest(); - flushHandler(); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_CANCELED); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_CANCELED); // Committed state is set back to the requested state once the override is cleared. - assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); + waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE))); assertEquals(mSysPropSetter.getValue(), DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -600,13 +601,9 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestBaseStateOverride(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); - // Flush the handler twice. The first flush ensures the request is added and the policy is - // notified, while the second flush ensures the callback is notified once the change is - // committed. - flushHandler(2 /* count */); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_ACTIVE); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_ACTIVE); // Committed state changes as there is a requested override. assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mSysPropSetter.getValue(), @@ -624,12 +621,11 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mService.getBinderService().cancelBaseStateOverride(); - flushHandler(); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_CANCELED); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_CANCELED); // Committed state is set back to the requested state once the override is cleared. - assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); + waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE))); assertEquals(mSysPropSetter.getValue(), DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -638,8 +634,8 @@ public final class DeviceStateManagerServiceTest { assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().baseState, - DEFAULT_DEVICE_STATE.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE.getIdentifier()); } @@ -660,13 +656,9 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestBaseStateOverride(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); - // Flush the handler twice. The first flush ensures the request is added and the policy is - // notified, while the second flush ensures the callback is notified once the change is - // committed. - flushHandler(2 /* count */); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_ACTIVE); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_ACTIVE); // Committed state changes as there is a requested override. assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mSysPropSetter.getValue(), @@ -684,12 +676,11 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(testDeviceState.getIdentifier()); - flushHandler(); - assertEquals(callback.getLastNotifiedStatus(token), - TestDeviceStateManagerCallback.STATUS_CANCELED); + waitAndAssert(() -> callback.getLastNotifiedStatus(token) + == TestDeviceStateManagerCallback.STATUS_CANCELED); // Committed state is set to the new base state once the override is cleared. - assertEquals(mService.getCommittedState(), Optional.of(testDeviceState)); + waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(testDeviceState))); assertEquals(mSysPropSetter.getValue(), testDeviceState.getIdentifier() + ":" + testDeviceState.getName()); assertEquals(mService.getBaseState(), Optional.of(testDeviceState)); @@ -698,8 +689,8 @@ public final class DeviceStateManagerServiceTest { assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), testDeviceState.getIdentifier()); - assertEquals(callback.getLastNotifiedInfo().baseState, - testDeviceState.getIdentifier()); + waitAndAssert(() -> callback.getLastNotifiedInfo().baseState + == testDeviceState.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, testDeviceState.getIdentifier()); } -- GitLab From 444e0d6c92038db69f058c18b2d0fa5fc342883d Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Tue, 9 May 2023 16:30:57 -0700 Subject: [PATCH 22/95] Change fingerprint reenrollment to acquired message Bug: 258901849 Test: Manual: Follow steps in https://docs.google.com/document/d/1wHQSAc0yvDBZdN4ZaXKaVi8vs1i5-u7Yh7rLfuJAQgc/edit#heading=h.c04xqjzf7saq, then unlock through keyguard, the notification should occur in 5 seconds of unlocking Test: atest BiometricNotificationServiceTest Test: atest BiometricNotificationDialogFactoryTest Change-Id: Iff606eb464132493b582e8e34a72c1f23472d088 --- .../BiometricFingerprintConstants.java | 9 +++++- .../BiometricNotificationService.java | 21 +++++++++++--- .../FingerprintReEnrollNotification.java | 29 +++++++++++++++++++ .../FingerprintReEnrollNotificationImpl.java | 29 +++++++++++++++++++ .../systemui/dagger/SystemUIModule.java | 4 +++ .../BiometricNotificationServiceTest.java | 25 ++++++++++++---- 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 257ad7162e9e..5b24fb6860a2 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -218,7 +218,8 @@ public interface BiometricFingerprintConstants { FINGERPRINT_ACQUIRED_UNKNOWN, FINGERPRINT_ACQUIRED_IMMOBILE, FINGERPRINT_ACQUIRED_TOO_BRIGHT, - FINGERPRINT_ACQUIRED_POWER_PRESSED}) + FINGERPRINT_ACQUIRED_POWER_PRESSED, + FINGERPRINT_ACQUIRED_RE_ENROLL}) @Retention(RetentionPolicy.SOURCE) @interface FingerprintAcquired {} @@ -309,6 +310,12 @@ public interface BiometricFingerprintConstants { */ int FINGERPRINT_ACQUIRED_POWER_PRESSED = 11; + /** + * This message is sent to encourage the user to re-enroll their fingerprints. + * @hide + */ + int FINGERPRINT_ACQUIRED_RE_ENROLL = 12; + /** * @hide */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java index dc874d83b75b..436da7cadf2f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java @@ -29,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.UserHandle; @@ -43,6 +42,9 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.policy.KeyguardStateController; + +import java.util.Optional; + import javax.inject.Inject; /** @@ -66,6 +68,7 @@ public class BiometricNotificationService implements CoreStartable { private final Handler mHandler; private final NotificationManager mNotificationManager; private final BiometricNotificationBroadcastReceiver mBroadcastReceiver; + private final FingerprintReEnrollNotification mFingerprintReEnrollNotification; private NotificationChannel mNotificationChannel; private boolean mFaceNotificationQueued; private boolean mFingerprintNotificationQueued; @@ -102,8 +105,15 @@ public class BiometricNotificationService implements CoreStartable { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_REQUIRED, UserHandle.USER_CURRENT); - } else if (msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL - && biometricSourceType == BiometricSourceType.FINGERPRINT) { + } + } + + @Override + public void onBiometricHelp(int msgId, String helpString, + BiometricSourceType biometricSourceType) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT + && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired( + msgId)) { mFingerprintReenrollRequired = true; } } @@ -115,13 +125,16 @@ public class BiometricNotificationService implements CoreStartable { KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardStateController keyguardStateController, Handler handler, NotificationManager notificationManager, - BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver) { + BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, + Optional fingerprintReEnrollNotification) { mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardStateController = keyguardStateController; mHandler = handler; mNotificationManager = notificationManager; mBroadcastReceiver = biometricNotificationBroadcastReceiver; + mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse( + new FingerprintReEnrollNotificationImpl()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java new file mode 100644 index 000000000000..9050f26d39e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java @@ -0,0 +1,29 @@ +/* + * 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.biometrics; + +import android.hardware.biometrics.BiometricFingerprintConstants; + +/** + * Checks if the fingerprint HAL has sent a re-enrollment request. + */ +public interface FingerprintReEnrollNotification { + //TODO: Remove this class and add a constant in the HAL API instead (b/281841852) + /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */ + boolean isFingerprintReEnrollRequired( + @BiometricFingerprintConstants.FingerprintAcquired int msgId); +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java new file mode 100644 index 000000000000..1f86bc6ae298 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java @@ -0,0 +1,29 @@ +/* + * 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.biometrics; + +import android.hardware.biometrics.BiometricFingerprintConstants; + +/** + * Checks if the fingerprint HAL has sent a re-enrollment request. + */ +public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{ + @Override + public boolean isFingerprintReEnrollRequired(int msgId) { + return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 3a942bd8203f..a0ecb3defc36 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -35,6 +35,7 @@ import com.android.systemui.assist.AssistModule; import com.android.systemui.authentication.AuthenticationModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; +import com.android.systemui.biometrics.FingerprintReEnrollNotification; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.bouncer.ui.BouncerViewModule; @@ -297,6 +298,9 @@ public abstract class SystemUIModule { @BindsOptionalOf abstract SystemStatusAnimationScheduler optionalSystemStatusAnimationScheduler(); + @BindsOptionalOf + abstract FingerprintReEnrollNotification optionalFingerprintReEnrollNotification(); + @SysUISingleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java index b0d006353a98..8d596da96a3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java @@ -52,6 +52,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Optional; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -65,11 +67,16 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { KeyguardStateController mKeyguardStateController; @Mock NotificationManager mNotificationManager; + @Mock + Optional mFingerprintReEnrollNotificationOptional; + @Mock + FingerprintReEnrollNotification mFingerprintReEnrollNotification; private static final String TAG = "BiometricNotificationService"; private static final int FACE_NOTIFICATION_ID = 1; private static final int FINGERPRINT_NOTIFICATION_ID = 2; private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds + private static final int FINGERPRINT_ACQUIRED_RE_ENROLL = 0; private final ArgumentCaptor mNotificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); @@ -80,14 +87,22 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { @Before public void setUp() { + when(mFingerprintReEnrollNotificationOptional.orElse(any())) + .thenReturn(mFingerprintReEnrollNotification); + when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired( + FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true); + mLooper = TestableLooper.get(this); Handler handler = new Handler(mLooper.getLooper()); BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory(); BiometricNotificationBroadcastReceiver broadcastReceiver = new BiometricNotificationBroadcastReceiver(mContext, dialogFactory); - mBiometricNotificationService = new BiometricNotificationService(mContext, - mKeyguardUpdateMonitor, mKeyguardStateController, handler, - mNotificationManager, broadcastReceiver); + mBiometricNotificationService = + new BiometricNotificationService(mContext, + mKeyguardUpdateMonitor, mKeyguardStateController, handler, + mNotificationManager, + broadcastReceiver, + mFingerprintReEnrollNotificationOptional); mBiometricNotificationService.start(); ArgumentCaptor updateMonitorCallbackArgumentCaptor = @@ -108,8 +123,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { public void testShowFingerprintReEnrollNotification() { when(mKeyguardStateController.isShowing()).thenReturn(false); - mKeyguardUpdateMonitorCallback.onBiometricError( - BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL, + mKeyguardUpdateMonitorCallback.onBiometricHelp( + FINGERPRINT_ACQUIRED_RE_ENROLL, "Testing Fingerprint Re-enrollment" /* errString */, BiometricSourceType.FINGERPRINT ); -- GitLab From a02b386d679d26201ce6624daf6fb0cbd1680f77 Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Thu, 15 Jun 2023 15:42:17 -0700 Subject: [PATCH 23/95] Remove duplicate face re-enroll notification Cancel re-enroll notification when face is removed. Test: atest BiometricsNotificationService Fixes: 286759227 Change-Id: I27d6bb843b01a844e44d9445bef4e24c38a2df56 --- .../BiometricNotificationService.java | 45 +++++++++-- .../BiometricNotificationServiceTest.java | 77 ++++++++++++++++++- .../face/aidl/FaceAuthenticationClient.java | 4 - 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java index 436da7cadf2f..c2b9102cbcb2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java @@ -21,6 +21,8 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -30,6 +32,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.BiometricStateListener; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -42,7 +47,6 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.policy.KeyguardStateController; - import java.util.Optional; import javax.inject.Inject; @@ -69,6 +73,8 @@ public class BiometricNotificationService implements CoreStartable { private final NotificationManager mNotificationManager; private final BiometricNotificationBroadcastReceiver mBroadcastReceiver; private final FingerprintReEnrollNotification mFingerprintReEnrollNotification; + private final FingerprintManager mFingerprintManager; + private final FaceManager mFaceManager; private NotificationChannel mNotificationChannel; private boolean mFaceNotificationQueued; private boolean mFingerprintNotificationQueued; @@ -119,14 +125,29 @@ public class BiometricNotificationService implements CoreStartable { } }; + private final BiometricStateListener mFaceStateListener = new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mNotificationManager.cancelAsUser(TAG, FACE_NOTIFICATION_ID, UserHandle.CURRENT); + } + }; + + private final BiometricStateListener mFingerprintStateListener = new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mNotificationManager.cancelAsUser(TAG, FINGERPRINT_NOTIFICATION_ID, UserHandle.CURRENT); + } + }; @Inject - public BiometricNotificationService(Context context, - KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardStateController keyguardStateController, - Handler handler, NotificationManager notificationManager, - BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, - Optional fingerprintReEnrollNotification) { + public BiometricNotificationService(@NonNull Context context, + @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, + @NonNull KeyguardStateController keyguardStateController, + @NonNull Handler handler, @NonNull NotificationManager notificationManager, + @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, + @NonNull Optional fingerprintReEnrollNotification, + @Nullable FingerprintManager fingerprintManager, + @Nullable FaceManager faceManager) { mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardStateController = keyguardStateController; @@ -135,6 +156,8 @@ public class BiometricNotificationService implements CoreStartable { mBroadcastReceiver = biometricNotificationBroadcastReceiver; mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse( new FingerprintReEnrollNotificationImpl()); + mFingerprintManager = fingerprintManager; + mFaceManager = faceManager; } @Override @@ -148,12 +171,19 @@ public class BiometricNotificationService implements CoreStartable { intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG); mContext.registerReceiver(mBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED_UNAUDITED); + if (mFingerprintManager != null) { + mFingerprintManager.registerBiometricStateListener(mFingerprintStateListener); + } + if (mFaceManager != null) { + mFaceManager.registerBiometricStateListener(mFaceStateListener); + } Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED, UserHandle.USER_CURRENT); } private void queueFaceReenrollNotification() { + Log.d(TAG, "Face re-enroll notification queued."); mFaceNotificationQueued = true; final String title = mContext.getString(R.string.face_re_enroll_notification_title); final String content = mContext.getString( @@ -166,6 +196,7 @@ public class BiometricNotificationService implements CoreStartable { } private void queueFingerprintReenrollNotification() { + Log.d(TAG, "Fingerprint re-enroll notification queued."); mFingerprintNotificationQueued = true; final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title); final String content = mContext.getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java index 8d596da96a3d..3c106789290d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java @@ -30,9 +30,12 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationManager; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.BiometricStateListener; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Handler; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -71,6 +74,10 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { Optional mFingerprintReEnrollNotificationOptional; @Mock FingerprintReEnrollNotification mFingerprintReEnrollNotification; + @Mock + FingerprintManager mFingerprintManager; + @Mock + FaceManager mFaceManager; private static final String TAG = "BiometricNotificationService"; private static final int FACE_NOTIFICATION_ID = 1; @@ -84,6 +91,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; private KeyguardStateController.Callback mKeyguardStateControllerCallback; private BiometricNotificationService mBiometricNotificationService; + private BiometricStateListener mFaceStateListener; + private BiometricStateListener mFingerprintStateListener; @Before public void setUp() { @@ -102,25 +111,37 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { mKeyguardUpdateMonitor, mKeyguardStateController, handler, mNotificationManager, broadcastReceiver, - mFingerprintReEnrollNotificationOptional); + mFingerprintReEnrollNotificationOptional, + mFingerprintManager, + mFaceManager); mBiometricNotificationService.start(); ArgumentCaptor updateMonitorCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); ArgumentCaptor stateControllerCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + ArgumentCaptor faceStateListenerArgumentCaptor = + ArgumentCaptor.forClass(BiometricStateListener.class); + ArgumentCaptor fingerprintStateListenerArgumentCaptor = + ArgumentCaptor.forClass(BiometricStateListener.class); verify(mKeyguardUpdateMonitor).registerCallback( updateMonitorCallbackArgumentCaptor.capture()); verify(mKeyguardStateController).addCallback( stateControllerCallbackArgumentCaptor.capture()); + verify(mFaceManager).registerBiometricStateListener( + faceStateListenerArgumentCaptor.capture()); + verify(mFingerprintManager).registerBiometricStateListener( + fingerprintStateListenerArgumentCaptor.capture()); + mFaceStateListener = faceStateListenerArgumentCaptor.getValue(); + mFingerprintStateListener = fingerprintStateListenerArgumentCaptor.getValue(); mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue(); mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue(); } @Test - public void testShowFingerprintReEnrollNotification() { + public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() { when(mKeyguardStateController.isShowing()).thenReturn(false); mKeyguardUpdateMonitorCallback.onBiometricHelp( @@ -142,7 +163,7 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG); } @Test - public void testShowFaceReEnrollNotification() { + public void testShowFaceReEnrollNotification_onErrorReEnroll() { when(mKeyguardStateController.isShowing()).thenReturn(false); mKeyguardUpdateMonitorCallback.onBiometricError( @@ -164,6 +185,54 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { .isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG); } + @Test + public void testCancelReEnrollmentNotification_onFaceEnrollmentStateChange() { + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mKeyguardUpdateMonitorCallback.onBiometricError( + BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL, + "Testing Face Re-enrollment" /* errString */, + BiometricSourceType.FACE + ); + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS); + mLooper.processAllMessages(); + + verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID), + mNotificationArgumentCaptor.capture(), any()); + + mFaceStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */, + false /* hasEnrollments */); + + verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID), + eq(UserHandle.CURRENT)); + } + + @Test + public void testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange() { + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mKeyguardUpdateMonitorCallback.onBiometricHelp( + FINGERPRINT_ACQUIRED_RE_ENROLL, + "Testing Fingerprint Re-enrollment" /* errString */, + BiometricSourceType.FINGERPRINT + ); + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS); + mLooper.processAllMessages(); + + verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID), + mNotificationArgumentCaptor.capture(), any()); + + mFingerprintStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */, + false /* hasEnrollments */); + + verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID), + eq(UserHandle.CURRENT)); + } + @Test public void testResetFaceUnlockReEnroll_onStart() { when(mKeyguardStateController.isShowing()).thenReturn(false); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 50d375c56f4a..35fc43ae5f74 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -43,7 +43,6 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; -import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; @@ -242,9 +241,6 @@ class FaceAuthenticationClient extends AuthenticationClient Date: Thu, 31 Aug 2023 12:53:16 +0000 Subject: [PATCH 24/95] Add a 60 days retention period to BMM Events Test: manual testing(set a 5 minutes retention period, factory reset the test device, go trough SUW, confirm that the logs show in backup dumpsys, confirm that logs are deleted after 5 minutes) atest CtsBackupHostTestCases, GtsBackupHostTestCases atest BackupManagerMonitorDumpsysUtilsTest, BackupManagerMonitorEventSenderTest, UserBackupManagerServiceTest Bug: 297159898 Change-Id: If8ce57c68cf9ffaa5472e5e14aee72c6bf2e5153 --- .../backup/UserBackupManagerService.java | 16 ++ .../BackupManagerMonitorDumpsysUtils.java | 166 ++++++++++++++- .../BackupManagerMonitorDumpsysUtilsTest.java | 201 +++++++++++++++++- 3 files changed, 364 insertions(+), 19 deletions(-) diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 4c137bce4f7b..2d80af92eea5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -654,6 +654,13 @@ public class UserBackupManagerService { // the pending backup set mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS); + // check if we are past the retention period for BMM Events, + // if so delete expired events and do not print them to dumpsys + BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils = + new BackupManagerMonitorDumpsysUtils(); + mBackupHandler.postDelayed(backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents, + INITIALIZATION_DELAY_MILLIS); + mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir); // Power management @@ -4181,7 +4188,16 @@ public class UserBackupManagerService { private void dumpBMMEvents(PrintWriter pw) { BackupManagerMonitorDumpsysUtils bm = new BackupManagerMonitorDumpsysUtils(); + if (bm.deleteExpiredBMMEvents()) { + pw.println("BACKUP MANAGER MONITOR EVENTS HAVE EXPIRED"); + return; + } File events = bm.getBMMEventsFile(); + if (events.length() == 0){ + // We have not recorded BMMEvents yet. + pw.println("NO BACKUP MANAGER MONITOR EVENTS"); + return; + } pw.println("START OF BACKUP MANAGER MONITOR EVENTS"); try (BufferedReader reader = new BufferedReader(new FileReader(events))) { String line; diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index 0b55ca21371b..bc2326d8241d 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -23,15 +23,22 @@ import android.os.Bundle; import android.os.Environment; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Map; +import java.util.concurrent.TimeUnit; /* @@ -46,12 +53,21 @@ public class BackupManagerMonitorDumpsysUtils { // Name of the subdirectory where the text file containing the BMM events will be stored. // Same as {@link UserBackupManagerFiles} private static final String BACKUP_PERSISTENT_DIR = "backup"; + private static final String INITIAL_SETUP_TIMESTAMP_KEY = "initialSetupTimestamp"; + // Retention period of 60 days (in millisec) for the BMM Events. + // After tha time has passed the text file containing the BMM events will be emptied + private static final long LOGS_RETENTION_PERIOD_MILLISEC = 60 * TimeUnit.DAYS.toMillis(1); + // We cache the value of IsAfterRetentionPeriod() to avoid unnecessary disk I/O + // mIsAfterRetentionPeriodCached tracks if we have cached the value of IsAfterRetentionPeriod() + private boolean mIsAfterRetentionPeriodCached = false; + // The cahched value of IsAfterRetentionPeriod() + private boolean mIsAfterRetentionPeriod; /** * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that * will be persisted in a text file and printed in the dumpsys. * - * If the evenntBundle passed is not a RESTORE event, return early + * If the eventBundle passed is not a RESTORE event, return early * * Key information related to the event: * - Timestamp (HAS TO ALWAYS BE THE FIRST LINE OF EACH EVENT) @@ -63,16 +79,21 @@ public class BackupManagerMonitorDumpsysUtils { * * Example of formatting: * RESTORE Event: [2023-08-18 17:16:00.735] Agent - Agent logging results - * Package name: com.android.wallpaperbackup - * Agent Logs: - * Data Type: wlp_img_system - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 - * Data Type: wlp_img_lock - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 + * Package name: com.android.wallpaperbackup + * Agent Logs: + * Data Type: wlp_img_system + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 + * Data Type: wlp_img_lock + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 */ public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) { + if (isAfterRetentionPeriod()) { + // We only log data for the first 60 days since setup + return; + } + if (eventBundle == null) { return; } @@ -89,8 +110,14 @@ public class BackupManagerMonitorDumpsysUtils { } File bmmEvents = getBMMEventsFile(); + if (bmmEvents.length() == 0) { + // We are parsing the first restore event. + // Time to also record the setup timestamp of the device + recordSetUpTimestamp(); + } + try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true); - PrintWriter pw = new FastPrintWriter(out);) { + PrintWriter pw = new FastPrintWriter(out);) { int eventCategory = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY); int eventId = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID); @@ -257,4 +284,123 @@ public class BackupManagerMonitorDumpsysUtils { default -> false; }; } + + /** + * Store the timestamp when the device was set up (date when the first BMM event is parsed) + * in a text file. + */ + @VisibleForTesting + void recordSetUpTimestamp() { + File setupDateFile = getSetUpDateFile(); + // record setup timestamp only once + if (setupDateFile.length() == 0) { + try (FileOutputStream out = new FileOutputStream(setupDateFile, /*append*/ true); + PrintWriter pw = new FastPrintWriter(out);) { + long currentDate = System.currentTimeMillis(); + pw.println(currentDate); + } catch (IOException e) { + Slog.w(TAG, "An error occurred while recording the setup date: " + + e.getMessage()); + } + } + + } + + @VisibleForTesting + String getSetUpDate() { + File fname = getSetUpDateFile(); + try (FileInputStream inputStream = new FileInputStream(fname); + InputStreamReader reader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(reader);) { + return bufferedReader.readLine(); + } catch (Exception e) { + Slog.w(TAG, "An error occurred while reading the date: " + e.getMessage()); + return "Could not retrieve setup date"; + } + } + + @VisibleForTesting + static boolean isDateAfterNMillisec(long startTimeStamp, long endTimeStamp, long millisec) { + if (startTimeStamp > endTimeStamp) { + // Something has gone wrong, timeStamp1 should always precede timeStamp2. + // Out of caution return true: we would delete the logs rather than + // risking them being kept for longer than the retention period + return true; + } + long timeDifferenceMillis = endTimeStamp - startTimeStamp; + return (timeDifferenceMillis >= millisec); + } + + /** + * Check if current date is after retention period + */ + @VisibleForTesting + boolean isAfterRetentionPeriod() { + if (mIsAfterRetentionPeriodCached) { + return mIsAfterRetentionPeriod; + } else { + File setUpDateFile = getSetUpDateFile(); + if (setUpDateFile.length() == 0) { + // We are yet to record a setup date. This means we haven't parsed the first event. + mIsAfterRetentionPeriod = false; + mIsAfterRetentionPeriodCached = true; + return false; + } + try { + long setupTimestamp = Long.parseLong(getSetUpDate()); + long currentTimestamp = System.currentTimeMillis(); + mIsAfterRetentionPeriod = isDateAfterNMillisec(setupTimestamp, currentTimestamp, + getRetentionPeriodInMillisec()); + mIsAfterRetentionPeriodCached = true; + return mIsAfterRetentionPeriod; + } catch (NumberFormatException e) { + // An error occurred when parsing the setup timestamp. + // Out of caution return true: we would delete the logs rather than + // risking them being kept for longer than the retention period + mIsAfterRetentionPeriod = true; + mIsAfterRetentionPeriodCached = true; + return true; + } + } + } + + @VisibleForTesting + File getSetUpDateFile() { + File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR); + File setupDateFile = new File(dataDir, INITIAL_SETUP_TIMESTAMP_KEY + ".txt"); + return setupDateFile; + } + + @VisibleForTesting + long getRetentionPeriodInMillisec() { + return LOGS_RETENTION_PERIOD_MILLISEC; + } + + /** + * Delete the BMM Events file after the retention period has passed. + * + * @return true if the retention period has passed false otherwise. + * we want to return true even if we were unable to delete the file, as this will prevent + * expired BMM events from being printed to the dumpsys + */ + public boolean deleteExpiredBMMEvents() { + try { + if (isAfterRetentionPeriod()) { + File bmmEvents = getBMMEventsFile(); + if (bmmEvents.exists()) { + if (bmmEvents.delete()) { + Slog.i(TAG, "Deleted expired BMM Events"); + } else { + Slog.e(TAG, "Unable to delete expired BMM Events"); + } + } + return true; + } + return false; + } catch (Exception e) { + // Handle any unexpected exceptions + // To be safe we return true as we want to avoid exposing expired BMMEvents + return true; + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java index 8e17b3a58769..a45b17e2e3c5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java @@ -17,26 +17,29 @@ package com.android.server.backup.utils; import static org.junit.Assert.assertTrue; - +import static org.testng.AssertJUnit.assertFalse; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupManagerMonitor; import android.os.Bundle; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; - import java.io.File; public class BackupManagerMonitorDumpsysUtilsTest { - private File mTempFile; + private long mRetentionPeriod; + private File mTempBMMEventsFile; + private File mTempSetUpDateFile; private TestBackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils; @Rule public TemporaryFolder tmp = new TemporaryFolder(); @Before public void setUp() throws Exception { - mTempFile = tmp.newFile("testbmmevents.txt"); + mRetentionPeriod = 30 * 60 * 1000; + mTempBMMEventsFile = tmp.newFile("testbmmevents.txt"); + mTempSetUpDateFile = tmp.newFile("testSetUpDate.txt"); mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils(); } @@ -46,7 +49,7 @@ public class BackupManagerMonitorDumpsysUtilsTest { throws Exception { mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(null); - assertTrue(mTempFile.length() == 0); + assertTrue(mTempBMMEventsFile.length() == 0); } @@ -57,7 +60,7 @@ public class BackupManagerMonitorDumpsysUtilsTest { event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1); mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); - assertTrue(mTempFile.length() == 0); + assertTrue(mTempBMMEventsFile.length() == 0); } @Test @@ -67,18 +70,198 @@ public class BackupManagerMonitorDumpsysUtilsTest { event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1); mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); - assertTrue(mTempFile.length() == 0); + assertTrue(mTempBMMEventsFile.length() == 0); + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_eventWithCategoryAndId_eventIsWrittenToFile() + throws Exception { + Bundle event = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + + assertTrue(mTempBMMEventsFile.length() != 0); + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_firstEvent_recordSetUpTimestamp() + throws Exception { + assertTrue(mTempBMMEventsFile.length()==0); + assertTrue(mTempSetUpDateFile.length()==0); + + Bundle event = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + + assertTrue(mTempBMMEventsFile.length() != 0); + assertTrue(mTempSetUpDateFile.length()!=0); + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_notFirstEvent_doNotChangeSetUpTimestamp() + throws Exception { + Bundle event1 = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event1); + String setUpTimestampBefore = mBackupManagerMonitorDumpsysUtils.getSetUpDate(); + + Bundle event2 = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event2); + String setUpTimestampAfter = mBackupManagerMonitorDumpsysUtils.getSetUpDate(); + + assertTrue(setUpTimestampBefore.equals(setUpTimestampAfter)); + } + + + @Test + public void deleteExpiredBackupManagerMonitorEvent_eventsAreExpired_deleteEventsAndReturnTrue() + throws Exception { + Bundle event = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + assertTrue(mTempBMMEventsFile.length() != 0); + // Re-initialise the test BackupManagerMonitorDumpsysUtils to + // clear the cached value of isAfterRetentionPeriod + mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils(); + + // set a retention period of 0 second + mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(0); + + assertTrue(mBackupManagerMonitorDumpsysUtils.deleteExpiredBMMEvents()); + assertFalse(mTempBMMEventsFile.exists()); + } + + @Test + public void deleteExpiredBackupManagerMonitorEvent_eventsAreNotExpired_returnFalse() throws + Exception { + Bundle event = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + assertTrue(mTempBMMEventsFile.length() != 0); + + // set a retention period of 30 minutes + mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(30 * 60 * 1000); + + assertFalse(mBackupManagerMonitorDumpsysUtils.deleteExpiredBMMEvents()); + assertTrue(mTempBMMEventsFile.length() != 0); + } + + @Test + public void isAfterRetentionPeriod_afterRetentionPeriod_returnTrue() throws + Exception { + mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp(); + + // set a retention period of 0 second + mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(0); + + assertTrue(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod()); + } + + @Test + public void isAfterRetentionPeriod_beforeRetentionPeriod_returnFalse() throws + Exception { + mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp(); + + // set a retention period of 30 minutes + mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(30 * 60 * 1000); + + assertFalse(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod()); + } + + @Test + public void isAfterRetentionPeriod_noSetupDate_returnFalse() throws + Exception { + assertTrue(mTempSetUpDateFile.length() == 0); + + assertFalse(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod()); + } + + @Test + public void isDateAfterNMillisec_date1IsAfterThanDate2_returnTrue() throws + Exception { + long timestamp1 = System.currentTimeMillis(); + long timestamp2 = timestamp1 - 1; + + assertTrue(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2, + 0)); + } + + @Test + public void isDateAfterNMillisec_date1IsAfterNMillisecFromDate2_returnTrue() throws + Exception { + long timestamp1 = System.currentTimeMillis(); + long timestamp2 = timestamp1 + 10; + + assertTrue(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2, + 10)); + } + + @Test + public void isDateAfterNMillisec_date1IsLessThanNMillisecFromDate2_returnFalse() throws + Exception { + long timestamp1 = System.currentTimeMillis(); + long timestamp2 = timestamp1 + 10; + + assertFalse(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2, + 11)); + } + + @Test + public void recordSetUpTimestamp_timestampNotSetBefore_setTimestamp() throws + Exception { + assertTrue(mTempSetUpDateFile.length() == 0); + + mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp(); + + assertTrue(mTempSetUpDateFile.length() != 0); + } + + @Test + public void recordSetUpTimestamp_timestampSetBefore_doNothing() throws + Exception { + mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp(); + assertTrue(mTempSetUpDateFile.length() != 0); + String timestampBefore = mBackupManagerMonitorDumpsysUtils.getSetUpDate(); + + mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp(); + + assertTrue(mTempSetUpDateFile.length() != 0); + String timestampAfter = mBackupManagerMonitorDumpsysUtils.getSetUpDate(); + assertTrue(timestampAfter.equals(timestampBefore)); + } + + private Bundle createRestoreBMMEvent() { + Bundle event = new Bundle(); + event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1); + event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1); + event.putInt(BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, + BackupAnnotations.OperationType.RESTORE); + return event; } private class TestBackupManagerMonitorDumpsysUtils extends BackupManagerMonitorDumpsysUtils { + + private long testRetentionPeriod; + TestBackupManagerMonitorDumpsysUtils() { super(); + this.testRetentionPeriod = mRetentionPeriod; + } + + public void setTestRetentionPeriod(long testRetentionPeriod) { + this.testRetentionPeriod = testRetentionPeriod; } @Override public File getBMMEventsFile() { - return mTempFile; + return mTempBMMEventsFile; } + + @Override + File getSetUpDateFile() { + return mTempSetUpDateFile; + } + + @Override + long getRetentionPeriodInMillisec() { + return testRetentionPeriod; + } + } } -- GitLab From 3748d0e3144313b5cbba6215feb0b8789d67c296 Mon Sep 17 00:00:00 2001 From: Justin Chung Date: Mon, 4 Sep 2023 16:35:51 +0900 Subject: [PATCH 25/95] Do not generate WTF for the SearchManager query 1. getSystemService() can generate WTF error, if service is not available from the device. But since search service is not available from the watch device, the query to the service should not print the WTF. 2. Still WTF was reported from the phone side too, due to the race of init order. To handle this, make PhoneWindowManager to not to call getSystemService for SearchService from init, but call in on demand. Bug: 297928943 Test: Manual test from SearchManager non-available device Change-Id: I7e3f7060f436aac28da8442773ca957c2e4cc172 --- core/java/android/app/SystemServiceRegistry.java | 6 ++++++ .../java/com/android/server/policy/PhoneWindowManager.java | 7 +++---- .../com/android/server/policy/TestPhoneWindowManager.java | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index cbbf4e0f9adc..fa52968e4554 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1647,6 +1647,12 @@ public final class SystemServiceRegistry { case Context.VIRTUALIZATION_SERVICE: case Context.VIRTUAL_DEVICE_SERVICE: return null; + case Context.SEARCH_SERVICE: + // Wear device does not support SEARCH_SERVICE so we do not print WTF here + PackageManager manager = ctx.getPackageManager(); + if (manager != null && manager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { + return null; + } } Slog.wtf(TAG, "Manager wrapper not available: " + name); return null; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b420acdc0850..b001c6aa95ef 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -438,7 +438,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mPreloadedRecentApps; final Object mServiceAcquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes - SearchManager mSearchManager; AccessibilityManager mAccessibilityManager; AccessibilityManagerInternal mAccessibilityManagerInternal; BurnInProtectionHelper mBurnInProtectionHelper; @@ -2217,7 +2216,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); - mSearchManager = mContext.getSystemService(SearchManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -4009,8 +4007,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { args.putLong(Intent.EXTRA_TIME, eventTime); args.putInt(AssistUtils.INVOCATION_TYPE_KEY, invocationType); - if (mSearchManager != null) { - mSearchManager.launchAssist(args); + SearchManager searchManager = mContext.getSystemService(SearchManager.class); + if (searchManager != null) { + searchManager.launchAssist(args); } else { // Fallback to status bar if search manager doesn't exist (e.g. on wear). StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 6d46d9ccf02e..35d7f01a5e64 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -447,7 +447,7 @@ class TestPhoneWindowManager { } void overrideSearchManager(SearchManager searchManager) { - mPhoneWindowManager.mSearchManager = searchManager; + doReturn(searchManager).when(mContext).getSystemService(eq(SearchManager.class)); } void assumeResolveActivityNotNull() { -- GitLab From df830e17f80b4d1e1f9d2334aa164baa31c49f85 Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Tue, 19 Sep 2023 17:35:41 +0200 Subject: [PATCH 26/95] Post ActivityLaunchAnimator timeout on Looper.mainLooper() This CL fixes weird shade state bugs that can happen during Activity launches animated by the ActivityLaunchAnimator. Before this CL, if a WindowManager timeout happened (i.e. WM never starts or cancels the remote animation) *and* that the launchContainer (the ViewRootImpl where the launch started) was detached, then the timeout would never be triggered, leaving our UI in an invalid state. This bug is currently easily reproducible using the steps from b/300056100#comment4, where we incorrectly animate an activity launch from a dialog then instantly dismiss that dialog. This CL fixes this bug by posting the timeout on Looper.getMainLooper() instead of using launchContainer.postDelayed(). That way, the timeout is always called after 1s if WM didn't start or cancel the remote animation. I suspect that this is somehow what is also happening with b/288507023, but I was not able to confirm this theory yet. Bug: b/300056100 Bug: b/288507023 Test: Manual, made sure that b/300056100#comment4 is not reproducible with this CL. Unfortunately ActivityLaunchAnimator is in a separate library so it can't use our DelayableExecutor (that is testable), so I don't think there is a good way to write a unit test for this. Change-Id: Iaa019d6d0aabdf97af0cb6995ad81270f275f3e7 --- .../animation/ActivityLaunchAnimator.kt | 34 +++++++++++++++---- .../animation/ActivityLaunchAnimatorTest.kt | 3 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index a95ab5565ab9..288b842d947a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -25,6 +25,7 @@ import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.os.Build +import android.os.Handler import android.os.Looper import android.os.RemoteException import android.util.Log @@ -58,7 +59,14 @@ class ActivityLaunchAnimator( /** The animator used when animating a Dialog into an app. */ // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to // TIMINGS.contentBeforeFadeOutDuration. - private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR + private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR, + + /** + * Whether we should disable the WindowManager timeout. This should be set to true in tests + * only. + */ + // TODO(b/301385865): Remove this flag. + private val disableWmTimeout: Boolean = false, ) { companion object { /** The timings when animating a View into an app. */ @@ -252,7 +260,7 @@ class ActivityLaunchAnimator( Log.d( TAG, "Calling controller.onIntentStarted(willAnimate=$willAnimate) " + - "[controller=$this]" + "[controller=$this]" ) } this.onIntentStarted(willAnimate) @@ -432,7 +440,8 @@ class ActivityLaunchAnimator( internal val delegate: AnimationDelegate init { - delegate = AnimationDelegate(controller, callback, listener, launchAnimator) + delegate = + AnimationDelegate(controller, callback, listener, launchAnimator, disableWmTimeout) } @BinderThread @@ -462,13 +471,26 @@ class ActivityLaunchAnimator( /** Listener for animation lifecycle events. */ private val listener: Listener? = null, /** The animator to use to animate the window launch. */ - private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR + private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR, + + /** + * Whether we should disable the WindowManager timeout. This should be set to true in tests + * only. + */ + // TODO(b/301385865): Remove this flag. + disableWmTimeout: Boolean = false, ) : RemoteAnimationDelegate { private val launchContainer = controller.launchContainer private val context = launchContainer.context private val transactionApplierView = controller.openingWindowSyncView ?: controller.launchContainer private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView) + private val timeoutHandler = + if (!disableWmTimeout) { + Handler(Looper.getMainLooper()) + } else { + null + } private val matrix = Matrix() private val invertMatrix = Matrix() @@ -488,11 +510,11 @@ class ActivityLaunchAnimator( @UiThread internal fun postTimeout() { - launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT) + timeoutHandler?.postDelayed(onTimeout, LAUNCH_TIMEOUT) } private fun removeTimeout() { - launchContainer.removeCallbacks(onTimeout) + timeoutHandler?.removeCallbacks(onTimeout) } @UiThread diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index cc004363a049..59c7e7669b63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -57,7 +57,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Before fun setup() { - activityLaunchAnimator = ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator) + activityLaunchAnimator = + ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator, disableWmTimeout = true) activityLaunchAnimator.callback = callback activityLaunchAnimator.addListener(listener) } -- GitLab From 37e0fd63c3928c076b93a9b19fd76972b01a6c60 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Wed, 20 Sep 2023 23:13:43 +0800 Subject: [PATCH 27/95] Only increase pending relaunch count if schedule is success Otherwise the client won't report finishRelaunching to decrease mPendingRelaunchCount and cause ActivityRecord#isSyncFinished to return false. Also skip pre-loading recents(home) if its process is still cached (e.g. intermediate state when switching user). Otherwise the transaction may be failed by frozen state. Bug: 301034389 Test: atest RecentsAnimationTest#testPreloadRecentsActivity Test: Create multiple users with using different font size, wallpaper, dark theme. Launch several apps on each users. Switch between the users multiple times. There won't be transition timeout when returning from other apps to home. Merged-In: Ia2761e1e9fadf98ab952440ae884c12cc78697c8 Change-Id: Ia2761e1e9fadf98ab952440ae884c12cc78697c8 --- data/etc/services.core.protolog.json | 6 ------ .../core/java/com/android/server/wm/ActivityRecord.java | 4 ++-- .../core/java/com/android/server/wm/RecentsAnimation.java | 6 ++++++ .../src/com/android/server/wm/RecentsAnimationTest.java | 6 ++++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 7d5c06cbed1a..13cf82e51b22 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2047,12 +2047,6 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", "at": "com\/android\/server\/wm\/TransitionController.java" }, - "-262984451": { - "message": "Relaunch failed %s", - "level": "INFO", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-251259736": { "message": "No longer freezing: %s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d231cf300806..ca3a84752fc4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9854,7 +9854,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" , (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6)); forceNewConfig = false; - startRelaunching(); final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults, pendingNewIntents, configChangeFlags, new MergedConfiguration(getProcessGlobalConfiguration(), @@ -9871,11 +9870,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A transaction.addCallback(callbackItem); transaction.setLifecycleStateRequest(lifecycleItem); mAtmService.getLifecycleManager().scheduleTransaction(transaction); + startRelaunching(); // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only // request resume if this activity is currently resumed, which implies we aren't // sleeping. } catch (RemoteException e) { - ProtoLog.i(WM_DEBUG_STATES, "Relaunch failed %s", e); + Slog.w(TAG, "Failed to relaunch " + this + ": " + e); } if (andResume) { diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index be9058840492..ee05e355e8ef 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -117,6 +118,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan return; } if (targetActivity.attachedToProcess()) { + if (targetActivity.app.getCurrentProcState() >= PROCESS_STATE_CACHED_ACTIVITY) { + Slog.v(TAG, "Skip preload recents for cached proc " + targetActivity.app); + // The process may be frozen that cannot receive binder call. + return; + } // The activity may be relaunched if it cannot handle the current configuration // changes. The activity will be paused state if it is relaunched, otherwise it // keeps the original stopped state. diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index de3a526573f8..491d5b56c8e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityManager.PROCESS_STATE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -148,8 +149,9 @@ public class RecentsAnimationTest extends WindowTestsBase { anyInt() /* startFlags */, any() /* profilerInfo */); // Assume its process is alive because the caller should be the recents service. - mSystemServicesTestRule.addProcess(aInfo.packageName, aInfo.processName, 12345 /* pid */, - aInfo.applicationInfo.uid); + final WindowProcessController proc = mSystemServicesTestRule.addProcess(aInfo.packageName, + aInfo.processName, 12345 /* pid */, aInfo.applicationInfo.uid); + proc.setCurrentProcState(PROCESS_STATE_HOME); Intent recentsIntent = new Intent().setComponent(mRecentsComponent); // Null animation indicates to preload. -- GitLab From 568961726dbe0b081fbbc25aa461891c2f8ff7ba Mon Sep 17 00:00:00 2001 From: Liz Kammer Date: Wed, 20 Sep 2023 15:15:43 +0000 Subject: [PATCH 28/95] Expand @FlaggedApi(FLAG) constants in API signature files The auto-generated Flags.FLAG_NAME constants are difficult to review in API tracking files. metalava will expand annotation arguments if (1) the field declaration is known to metalava, and (2) the constant is not part of the API surface. The auto-generated constants are hidden, so not part of any API surface. This satisfies (1). This CL adds the auto-generated sources to metalava's input. This satisfies (2). (cherry picked from commit 01544b94d3b7b90dcac6f62d9528ede9e6ef2838) Bug: 297881670 Test: m checkapi Test: m /etc/aconfig_flags.textproto & diff against a clean build Merged-In: I757c6e87d81768ef6095a4bea67c74c3ae6028a7 Change-Id: I757c6e87d81768ef6095a4bea67c74c3ae6028a7 --- AconfigFlags.bp | 23 +++++++++++++++-------- api/StubLibraries.bp | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 55f5436eed90..8670b4db2a5a 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -12,20 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +aconfig_srcjars = [ + ":android.os.flags-aconfig-java{.generated_srcjars}", + ":android.security.flags-aconfig-java{.generated_srcjars}", + ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", + ":com.android.hardware.input-aconfig-java{.generated_srcjars}", + ":com.android.text.flags-aconfig-java{.generated_srcjars}", +] + +filegroup { + name: "framework-minus-apex-aconfig-srcjars", + srcs: aconfig_srcjars, +} + // Aconfig declarations and libraries for the core framework java_defaults { name: "framework-minus-apex-aconfig-libraries", // Add java_aconfig_libraries to here to add them to the core framework - srcs: [ - ":android.os.flags-aconfig-java{.generated_srcjars}", - ":android.security.flags-aconfig-java{.generated_srcjars}", - ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}", - ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", - ":com.android.hardware.input-aconfig-java{.generated_srcjars}", - ":com.android.text.flags-aconfig-java{.generated_srcjars}", - ], // Add aconfig-annotations-lib as a dependency for the optimization + srcs: aconfig_srcjars, libs: ["aconfig-annotations-lib"], } diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index dc17a78a1cd9..c05d300fddb8 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -29,6 +29,9 @@ droidstubs { name: "api-stubs-docs-non-updatable", + srcs: [ + ":framework-minus-apex-aconfig-srcjars", + ], defaults: [ "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", -- GitLab From 42592cc89868349bb59bdf0c32cfb64b4b995584 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Thu, 21 Sep 2023 12:54:27 +0000 Subject: [PATCH 29/95] Catch NPE when reportInstallationToSecurityLog The scan result is null for installExistingPackageAsUser(), becuse it is installing a package that already exists, there is no scanning or parsing involved. Like reportInstallationStats, we also catch the exception temporary here. There is a follow up bug to handle it correctly. Bug: 301397206 Test: manual. Change-Id: Ie846264d2bc1cd300d67d5cf69e41b710716fa54 --- .../com/android/server/pm/PackageMetrics.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index c1580c4b1cb9..3a00dc7f8aeb 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -310,18 +310,25 @@ final class PackageMetrics { if (!SecurityLog.isLoggingEnabled()) { return; } - final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); - if (ps == null) { - return; - } - final String packageName = ps.getPackageName(); - final long versionCode = ps.getVersionCode(); - if (!mInstallRequest.isInstallReplace()) { - SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode, - userId); - } else { - SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode, - userId); + // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because + // the scan result is null for installExistingPackageAsUser(). Because it's installing + // a package that's already existing, there's no scanning or parsing involved + try { + final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); + if (ps == null) { + return; + } + final String packageName = ps.getPackageName(); + final long versionCode = ps.getVersionCode(); + if (!mInstallRequest.isInstallReplace()) { + SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode, + userId); + } else { + SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode, + userId); + } + } catch (IllegalStateException | NullPointerException e) { + // no-op } } -- GitLab From e3fd8d48bb81ea195d117bec8f4154302bb01f9c Mon Sep 17 00:00:00 2001 From: Ibrahim Yilmaz Date: Wed, 20 Sep 2023 17:37:46 +0000 Subject: [PATCH 30/95] Team Food qs_container_graph_optimizer This CL opens qs_container_graph_optimizer change to the team food. Bug: 287205379,298975758 Test: SystemUITests Change-Id: I6dd86e2ff3d2c910c5d4d07087376024124a5577 --- packages/SystemUI/src/com/android/systemui/flags/Flags.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 02575eb8adf4..07381bcd164e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -795,7 +795,8 @@ object Flags { // TODO(b/287205379): Tracking bug @JvmField - val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer") + val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer", + teamfood = true) /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ @JvmField -- GitLab From cc4e7ccceb662083d3c2731df03ae30eab9a422b Mon Sep 17 00:00:00 2001 From: Ibrahim Yilmaz Date: Wed, 20 Sep 2023 17:55:20 +0000 Subject: [PATCH 31/95] Team Food precomputed_text This CL opens precomputed_text change to the team food. Bug: 289250881, 289573946 Test: SystemUITests Change-Id: I6e389554fc6b2d2dfe36081b284c3e7447500579 --- packages/SystemUI/src/com/android/systemui/flags/Flags.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 02575eb8adf4..60c492888f59 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -757,7 +757,7 @@ object Flags { unreleasedFlag("enable_new_privacy_dialog", teamfood = true) // TODO(b/289573946): Tracking Bug - @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text") + @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true) // 2900 - CentralSurfaces-related flags -- GitLab From 98d050666d4f1d2adb19b77f27985f1e0156fef8 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Thu, 21 Sep 2023 12:58:50 +0000 Subject: [PATCH 32/95] Adjust to next focusable task if top task is died Since shell transition is enabled, there will also have a transition animation to handle app died. Then the died activity and task won't be removed immediately until the animation is finished. To be more specific, TaskDisplayArea#mPreferredTopFocusableRootTask is not changed to next task, so updateTopResumedActivityIfNeeded will early return without calling setLastResumedActivityUncheckLocked. Note that this will also change the animation style of the died task. Because previously the root task of home is not collected, the animation style is the default sliding out. Now the animation will match the remote animation rule of launcher so it will animate scaling to the icon on home. Bug: 301265227 Test: Launch Settings from home. adb shell am force-stop com.android.settings adb shell dumpsys window | grep mFocusedApp The output will show home activity. Change-Id: I8c99964df9e1f2d52779d41c23c6195fe3c87af3 --- .../com/android/server/wm/ActivityTaskManagerService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f38f6b0e4fa0..237bc9203415 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6420,6 +6420,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (!restarting && hasVisibleActivities) { deferWindowLayout(); try { + final Task topTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); + if (topTask != null + && topTask.topRunningActivity(true /* focusableOnly */) == null) { + topTask.adjustFocusToNextFocusableTask("handleAppDied"); + } if (!mRootWindowContainer.resumeFocusedTasksTopActivities()) { // If there was nothing to resume, and we are not already restarting // this process, but there is a visible activity that is hosted by the -- GitLab From 6fb5384ade3e9550479b6b2c23f3a6a86f7d9f98 Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Wed, 13 Sep 2023 13:09:54 +0000 Subject: [PATCH 33/95] [1/n] Optimize user aspect ratio button heuristic Don't allow the user aspect ratio settings button to be shown more than once for the current application visible to the user. Test: `atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest` `atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest` `atest WMShellUnitTests:CompatUIControllerTest` Fix: 300226988 Bug: 299078364 Bug: 289356588 Change-Id: Ib32df6b2ac6aaf7a7446f7b5be5cd684b9f7ebde --- core/java/android/app/TaskInfo.java | 1 + .../wm/shell/compatui/CompatUIController.java | 59 ++++++++- .../UserAspectRatioSettingsWindowManager.java | 18 ++- .../compatui/CompatUIControllerTest.java | 119 ++++++++++++++++++ .../UserAspectRatioSettingsLayoutTest.java | 3 +- ...rAspectRatioSettingsWindowManagerTest.java | 3 +- 6 files changed, 198 insertions(+), 5 deletions(-) diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 634089b73618..4957c3e2b81d 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -583,6 +583,7 @@ public class TaskInfo { == that.configuration.getLayoutDirection()) && (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode) && (!hasCompatUI() || isVisible == that.isVisible) + && isFocused == that.isFocused && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index c111ce623c1a..e0031b4db419 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -173,6 +173,18 @@ public class CompatUIController implements OnDisplaysChangedListener, // be shown. private boolean mKeyguardShowing; + /** + * The id of the task for the application we're currently attempting to show the user aspect + * ratio settings button for, or have most recently shown the button for. + */ + private int mTopActivityTaskId; + + /** + * Whether the user aspect ratio settings button has been shown for the current application + * associated with the task id stored in {@link CompatUIController#mTopActivityTaskId}. + */ + private boolean mHasShownUserAspectRatioSettingsButton = false; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -227,6 +239,11 @@ public class CompatUIController implements OnDisplaysChangedListener, if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); } + + if (taskInfo != null && taskListener != null) { + updateActiveTaskInfo(taskInfo); + } + if (taskInfo.configuration == null || taskListener == null) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); @@ -319,6 +336,45 @@ public class CompatUIController implements OnDisplaysChangedListener, forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId()))); } + /** + * Invoked when a new task is created or the info of an existing task has changed. Updates the + * shown status of the user aspect ratio settings button and the task id it relates to. + */ + void updateActiveTaskInfo(@NonNull TaskInfo taskInfo) { + // If the activity belongs to the task we are currently tracking, don't update any variables + // as they are still relevant. Else, if the activity is visible and focused (the one the + // user can see and is using), the user aspect ratio button can potentially be displayed so + // start tracking the buttons visibility for this task. + if (mTopActivityTaskId != taskInfo.taskId && taskInfo.isVisible && taskInfo.isFocused) { + mTopActivityTaskId = taskInfo.taskId; + setHasShownUserAspectRatioSettingsButton(false); + } + } + + /** + * Informs the system that the user aspect ratio button has been displayed for the application + * associated with the task id in {@link CompatUIController#mTopActivityTaskId}. + */ + void setHasShownUserAspectRatioSettingsButton(boolean state) { + mHasShownUserAspectRatioSettingsButton = state; + } + + /** + * Returns whether the user aspect ratio settings button has been show for the application + * associated with the task id in {@link CompatUIController#mTopActivityTaskId}. + */ + boolean hasShownUserAspectRatioSettingsButton() { + return mHasShownUserAspectRatioSettingsButton; + } + + /** + * Returns the task id of the application we are currently attempting to show, of have most + * recently shown, the user aspect ratio settings button for. + */ + int getTopActivityTaskId() { + return mTopActivityTaskId; + } + private boolean showOnDisplay(int displayId) { return !mKeyguardShowing && !isImeShowingOnDisplay(displayId); } @@ -569,7 +625,8 @@ public class CompatUIController implements OnDisplaysChangedListener, return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor, - mDisappearTimeSupplier); + mDisappearTimeSupplier, this::hasShownUserAspectRatioSettingsButton, + this::setHasShownUserAspectRatioSettingsButton); } private void launchUserAspectRatioSettings( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 77aefc8f7e4a..96470012af03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -37,7 +37,9 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; /** * Window manager for the user aspect ratio settings button which allows users to go to @@ -55,6 +57,12 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract private final ShellExecutor mShellExecutor; + @NonNull + private final Supplier mUserAspectRatioButtonShownChecker; + + @NonNull + private final Consumer mUserAspectRatioButtonStateConsumer; + @VisibleForTesting @NonNull final CompatUIHintsState mCompatUIHintsState; @@ -72,9 +80,13 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState, @NonNull BiConsumer onButtonClicked, @NonNull ShellExecutor shellExecutor, - @NonNull Function disappearTimeSupplier) { + @NonNull Function disappearTimeSupplier, + @NonNull Supplier userAspectRatioButtonStateChecker, + @NonNull Consumer userAspectRatioButtonShownConsumer) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mShellExecutor = shellExecutor; + mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker; + mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer; mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); mCompatUIHintsState = compatUIHintsState; mOnButtonClicked = onButtonClicked; @@ -185,6 +197,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract return; } mLayout.setUserAspectRatioButtonVisibility(true); + mUserAspectRatioButtonStateConsumer.accept(true); // Only show by default for the first time. if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) { mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); @@ -210,7 +223,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { return taskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.topActivityBoundsLetterboxed - || taskInfo.isUserFullscreenOverrideEnabled); + || taskInfo.isUserFullscreenOverrideEnabled) + && !mUserAspectRatioButtonShownChecker.get(); } private long getDisappearTimeMs() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 9b9600e4a51e..3e0c853cf7dd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -61,6 +61,7 @@ import com.android.wm.shell.transition.Transitions; import dagger.Lazy; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -523,13 +524,131 @@ public class CompatUIControllerTest extends ShellTestCase { .createLayout(anyBoolean()); } + @Test + public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() { + // Simulate user aspect ratio button being shown for previous task + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + } + + @Test + public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + final int newTaskId = TASK_ID + 1; + + // Create visible but NOT focused task + final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ false); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo1); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create focused but NOT visible task + final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo2); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create NOT focused but NOT visible task + final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false, + /* isFocused */ false); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo3); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + + @Test + public void testUpdateActiveTaskInfo_sameTask_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate same task being re-shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { + return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, + /* isVisible */ false, /* isFocused */ false); + } + + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState, boolean isVisible, + boolean isFocused) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.displayId = displayId; taskInfo.topActivityInSizeCompat = hasSizeCompat; taskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.isVisible = isVisible; + taskInfo.isFocused = isFocused; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java index ce1290b38830..f460d1b09e34 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -93,7 +93,8 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIController.CompatUIHintsState(), - mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0); + mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0, + () -> false, s -> {}); mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( R.layout.user_aspect_ratio_settings_layout, null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 08cc2f763135..2a9b72a0bc49 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -106,7 +106,8 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { false, /* topActivityBoundsLetterboxed */ true); mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), - mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0); + mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0, () -> false, + s -> {}); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); -- GitLab From 92210febda7b47c044870034daf1faa3f33935f9 Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Fri, 15 Sep 2023 11:14:38 +0000 Subject: [PATCH 34/95] [2/n] Optimize user aspect ratio button heuristic Don't show the user aspect ratio settings button after a transparent activity has been dismissed if it was previously shown for the previous activity. Test: atest WMShellUnitTests:CompatUIControllerTest Fix: 299080123 Bug: 289356588 Change-Id: Iab00c25cb0c105696bdd40dd4ebc35c5f34ed1b0 --- core/java/android/app/TaskInfo.java | 15 +++++- .../wm/shell/compatui/CompatUIController.java | 3 +- .../compatui/CompatUIControllerTest.java | 46 ++++++++++++++++++- .../core/java/com/android/server/wm/Task.java | 1 + 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 4957c3e2b81d..5f8b76508ef3 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -254,6 +254,12 @@ public class TaskInfo { */ public boolean isUserFullscreenOverrideEnabled; + /** + * Whether the top activity fillsParent() is false + * @hide + */ + public boolean isTopActivityTransparent; + /** * Hint about the letterbox state of the top activity. * @hide @@ -551,7 +557,8 @@ public class TaskInfo { && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId) && parentTaskId == that.parentTaskId && Objects.equals(topActivity, that.topActivity) - && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled; + && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled + && isTopActivityTransparent == that.isTopActivityTransparent; } /** @@ -584,7 +591,8 @@ public class TaskInfo { && (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode) && (!hasCompatUI() || isVisible == that.isVisible) && isFocused == that.isFocused - && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled; + && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled + && isTopActivityTransparent == that.isTopActivityTransparent; } /** @@ -641,6 +649,7 @@ public class TaskInfo { topActivityLetterboxWidth = source.readInt(); topActivityLetterboxHeight = source.readInt(); isUserFullscreenOverrideEnabled = source.readBoolean(); + isTopActivityTransparent = source.readBoolean(); } /** @@ -698,6 +707,7 @@ public class TaskInfo { dest.writeInt(topActivityLetterboxWidth); dest.writeInt(topActivityLetterboxHeight); dest.writeBoolean(isUserFullscreenOverrideEnabled); + dest.writeBoolean(isTopActivityTransparent); } @Override @@ -745,6 +755,7 @@ public class TaskInfo { + " topActivityLetterboxWidth=" + topActivityLetterboxWidth + " topActivityLetterboxHeight=" + topActivityLetterboxHeight + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled + + " isTopActivityTransparent=" + isTopActivityTransparent + " locusId=" + mTopActivityLocusId + " displayAreaFeatureId=" + displayAreaFeatureId + " cameraCompatControlState=" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index e0031b4db419..96f22c0bc97b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -345,7 +345,8 @@ public class CompatUIController implements OnDisplaysChangedListener, // as they are still relevant. Else, if the activity is visible and focused (the one the // user can see and is using), the user aspect ratio button can potentially be displayed so // start tracking the buttons visibility for this task. - if (mTopActivityTaskId != taskInfo.taskId && taskInfo.isVisible && taskInfo.isFocused) { + if (mTopActivityTaskId != taskInfo.taskId && !taskInfo.isTopActivityTransparent + && taskInfo.isVisible && taskInfo.isFocused) { mTopActivityTaskId = taskInfo.taskId; setHasShownUserAspectRatioSettingsButton(false); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 3e0c853cf7dd..f85d707d55f9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -633,15 +633,58 @@ public class CompatUIControllerTest extends ShellTestCase { Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); } + @Test + public void testUpdateActiveTaskInfo_transparentTask_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + final int newTaskId = TASK_ID + 1; + + // Create transparent task + final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true, /* isTopActivityTransparent */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo1); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, - /* isVisible */ false, /* isFocused */ false); + /* isVisible */ false, /* isFocused */ false, + /* isTopActivityTransparent */ false); } private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState, boolean isVisible, boolean isFocused) { + return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, + isVisible, isFocused, /* isTopActivityTransparent */ false); + } + + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState, boolean isVisible, + boolean isFocused, boolean isTopActivityTransparent) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.displayId = displayId; @@ -649,6 +692,7 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.cameraCompatControlState = cameraCompatControlState; taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; + taskInfo.isTopActivityTransparent = isTopActivityTransparent; return taskInfo; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9f2aff28cb11..a6eace7ff8d1 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3461,6 +3461,7 @@ class Task extends TaskFragment { info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET; info.isUserFullscreenOverrideEnabled = top != null && top.mLetterboxUiController.shouldApplyUserFullscreenOverride(); + info.isTopActivityTransparent = top != null && !top.fillsParent(); info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap(); if (info.isLetterboxDoubleTapEnabled) { info.topActivityLetterboxWidth = top.getBounds().width(); -- GitLab From 309a61bd1cb9f3341e2fcdb6f7db55b611f1380f Mon Sep 17 00:00:00 2001 From: Santiago Seifert Date: Thu, 21 Sep 2023 13:43:34 +0000 Subject: [PATCH 35/95] Converge documentation around media session key As a collateral effect, this change also fixes some javadocs. Bug: 237533067 Test: N/A. Javadoc only change. Change-Id: I960e719cba66056c0ae6db3a265550ac9c17c905 --- core/java/android/view/KeyEvent.java | 19 +++++++++- media/java/android/media/AudioManager.java | 26 ++++--------- .../java/android/media/RemoteController.java | 38 +++++++------------ .../server/media/MediaKeyDispatcher.java | 21 ++-------- 4 files changed, 41 insertions(+), 63 deletions(-) diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index bb4cc8fc8d45..b17d2d1800e5 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1990,8 +1990,23 @@ public class KeyEvent extends InputEvent implements Parcelable { } /** - * Returns whether this key will be sent to the - * {@link android.media.session.MediaSession.Callback} if not handled. + * Returns whether this key will be sent to the {@link + * android.media.session.MediaSession.Callback} if not handled. + * + *

The following key codes are considered {@link android.media.session.MediaSession} keys: + * + *

    + *
  • {@link #KEYCODE_MEDIA_PLAY} + *
  • {@link #KEYCODE_MEDIA_PAUSE} + *
  • {@link #KEYCODE_MEDIA_PLAY_PAUSE} + *
  • {@link #KEYCODE_HEADSETHOOK} + *
  • {@link #KEYCODE_MEDIA_STOP} + *
  • {@link #KEYCODE_MEDIA_NEXT} + *
  • {@link #KEYCODE_MEDIA_PREVIOUS} + *
  • {@link #KEYCODE_MEDIA_REWIND} + *
  • {@link #KEYCODE_MEDIA_RECORD} + *
  • {@link #KEYCODE_MEDIA_FAST_FORWARD} + *
*/ public static final boolean isMediaSessionKey(int keyCode) { switch (keyCode) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 842542f4f43b..adc0e16448ee 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -941,27 +941,15 @@ public class AudioManager { } /** - * Sends a simulated key event for a media button. - * To simulate a key press, you must first send a KeyEvent built with a - * {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} - * action. + * Sends a simulated key event for a media button. To simulate a key press, you must first send + * a KeyEvent built with a {@link KeyEvent#ACTION_DOWN} action, then another event with the + * {@link KeyEvent#ACTION_UP} action. + * *

The key event will be sent to the current media key event consumer which registered with * {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}. - * @param keyEvent a {@link KeyEvent} instance whose key code is one of - * {@link KeyEvent#KEYCODE_MUTE}, - * {@link KeyEvent#KEYCODE_HEADSETHOOK}, - * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, - * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, - * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, - * {@link KeyEvent#KEYCODE_MEDIA_STOP}, - * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, - * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, - * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, - * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, - * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, - * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, - * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, - * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. + * + * @param keyEvent a media session {@link KeyEvent}, as defined by {@link + * KeyEvent#isMediaSessionKey}. */ public void dispatchMediaKeyEvent(KeyEvent keyEvent) { MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index a6e8fa0644df..aaaf25f008c7 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -220,33 +220,21 @@ import java.util.List; return -1; } - /** - * Send a simulated key event for a media button to be received by the current client. - * To simulate a key press, you must first send a KeyEvent built with - * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} - * action. - *

The key event will be sent to the registered receiver - * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated - * {@link RemoteControlClient}'s metadata and playback state is published (there may be - * none under some circumstances). - * @param keyEvent a {@link KeyEvent} instance whose key code is one of - * {@link KeyEvent#KEYCODE_MUTE}, - * {@link KeyEvent#KEYCODE_HEADSETHOOK}, - * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, - * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, - * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, - * {@link KeyEvent#KEYCODE_MEDIA_STOP}, - * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, - * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, - * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, - * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, - * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, - * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, - * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, - * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. + * Send a simulated key event for a media button to be received by the current client. To + * simulate a key press, you must first send a KeyEvent built with a {@link + * KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} action. + * + *

The key event will be sent to the registered receiver (see {@link + * AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated {@link + * RemoteControlClient}'s metadata and playback state is published (there may be none under some + * circumstances). + * + * @param keyEvent a media session {@link KeyEvent}, as defined by {@link + * KeyEvent#isMediaSessionKey}. * @return true if the event was successfully sent, false otherwise. - * @throws IllegalArgumentException + * @throws IllegalArgumentException If the provided {@link KeyEvent} is not a media session key, + * as defined by {@link KeyEvent#isMediaSessionKey}. */ public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException { if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java index 55511ad637ac..66cafabb1e6e 100644 --- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java +++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java @@ -117,24 +117,11 @@ public abstract class MediaKeyDispatcher { /** * Gets the map of key code -> {@link KeyEventType} that have been overridden. - *

- * The list of valid key codes are the following: - *

    - *
  • {@link KeyEvent#KEYCODE_MEDIA_PLAY} - *
  • {@link KeyEvent#KEYCODE_MEDIA_PAUSE} - *
  • {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE} - *
  • {@link KeyEvent#KEYCODE_MUTE} - *
  • {@link KeyEvent#KEYCODE_HEADSETHOOK} - *
  • {@link KeyEvent#KEYCODE_MEDIA_STOP} - *
  • {@link KeyEvent#KEYCODE_MEDIA_NEXT} - *
  • {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS} - *
  • {@link KeyEvent#KEYCODE_VOLUME_UP} - *
  • {@link KeyEvent#KEYCODE_VOLUME_DOWN} - *
  • {@link KeyEvent#KEYCODE_VOLUME_MUTE} - *
- * @see {@link KeyEvent#isMediaSessionKey(int)} + * + *

For the list of relevant key codes, see {@link KeyEvent#isMediaSessionKey(int)}. */ - @KeyEventType Map getOverriddenKeyEvents() { + @KeyEventType + Map getOverriddenKeyEvents() { return mOverriddenKeyEvents; } -- GitLab From 91171a4bfcc1ec937c4a3221270a47d3083ca46b Mon Sep 17 00:00:00 2001 From: Justin Chung Date: Thu, 21 Sep 2023 14:20:18 +0000 Subject: [PATCH 36/95] Inject mock VibratorInfo from TestPhoneWinowManager Bug: 300967518 Test: atest PowerKeyGestureTests#testPowerLongPress Change-Id: I6244d01c2d50bde753775073741bec3649fc322a --- .../src/com/android/server/policy/TestPhoneWindowManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index e301da7c58fc..0e0f64ddecd6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -77,6 +77,7 @@ import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.UserHandle; import android.os.Vibrator; +import android.os.VibratorInfo; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; import android.util.FeatureFlagUtils; @@ -138,6 +139,7 @@ class TestPhoneWindowManager { @Mock private TelecomManager mTelecomManager; @Mock private NotificationManager mNotificationManager; @Mock private Vibrator mVibrator; + @Mock private VibratorInfo mVibratorInfo; @Mock private PowerManager mPowerManager; @Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl; @Mock private InputMethodManagerInternal mInputMethodManagerInternal; @@ -246,6 +248,7 @@ class TestPhoneWindowManager { doReturn(mTelecomManager).when(mPhoneWindowManager).getTelecommService(); doNothing().when(mNotificationManager).silenceNotificationSound(); doReturn(mNotificationManager).when(mPhoneWindowManager).getNotificationService(); + doReturn(mVibratorInfo).when(mVibrator).getInfo(); doReturn(mVibrator).when(mContext).getSystemService(eq(Context.VIBRATOR_SERVICE)); final PowerManager.WakeLock wakeLock = mock(PowerManager.WakeLock.class); -- GitLab From 1c9ae2000596be1dda69a48666feca40c98d7680 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Thu, 21 Sep 2023 10:35:22 -0400 Subject: [PATCH 37/95] [Status bar] Reduce length of BatteryController log We don't need 300 lines anymore. 30 Seems reasonable since we have about 3 log lines per event, so this is effectively a 10-event buffer Test: manually check dumpsys Fixes: 300147438 Change-Id: I0ff8f5130b04d6f91318c7405bc2260519f69489 --- .../statusbar/policy/dagger/StatusBarPolicyModule.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 927024fbdd34..a77c69236946 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -213,12 +213,11 @@ public interface StatusBarPolicyModule { return networkController.getDataSaverController(); } - /** Provides a log bufffer for BatteryControllerImpl */ + /** Provides a log buffer for BatteryControllerImpl */ @Provides @SysUISingleton @BatteryControllerLog - //TODO(b/300147438): reduce the size of this log buffer static LogBuffer provideBatteryControllerLog(LogBufferFactory factory) { - return factory.create(BatteryControllerLogger.TAG, 300); + return factory.create(BatteryControllerLogger.TAG, 30); } } -- GitLab From 501eeee21a5fb200b88b42eb8ed53be67bd16e56 Mon Sep 17 00:00:00 2001 From: beatricemarch Date: Thu, 31 Aug 2023 12:53:16 +0000 Subject: [PATCH 38/95] Add more BackupManagerMonitor events to PerformUnifiedRestoreTask. In particular add events to cover -If this is KV or Full restore -For each package, when the restore started and when it ended -When a restore operation starts, if it is system restore or restore at install -Any errors Test: manual testing. Run `adb shell bmgr restore 1` and verify that the new restore events are added to the dumpsys atest CtsBackupHostTestCases, GtsBackupHostTestCases atest BackupManagerMonitorDumpsysUtilsTest, BackupManagerMonitorEventSenderTest, UserBackupManagerServiceTest, PerformUnifiedRestoreTaskTest, BmgrTest Bug: 290747920 Change-Id: I0d221f10932fea3e8fb90a1827c7f1b5bf21d25d --- .../src/com/android/commands/bmgr/Bmgr.java | 34 +++++ .../app/backup/BackupManagerMonitor.java | 51 ++++++++ .../restore/PerformUnifiedRestoreTask.java | 117 ++++++++++++++++++ .../BackupManagerMonitorDumpsysUtils.java | 26 ++++ 4 files changed, 228 insertions(+) diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index b6dc32a29f04..088372702ebc 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -1246,6 +1246,40 @@ public class Bmgr { return "TRANSPORT_IS_NULL"; case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS: return "AGENT_LOGGING_RESULTS"; + case BackupManagerMonitor.LOG_EVENT_ID_START_SYSTEM_RESTORE: + return "START_SYSTEM_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_START_RESTORE_AT_INSTALL: + return "START_RESTORE_AT_INSTALL"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE: + return "TRANSPORT_ERROR_DURING_START_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME: + return "CANNOT_GET_NEXT_PKG_NAME"; + case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE: + return "UNKNOWN_RESTORE_TYPE"; + case BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE: + return "KV_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE: + return "FULL_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET: + return "NO_NEXT_RESTORE_TARGET"; + case BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR: + return "KV_AGENT_ERROR"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED: + return "PACKAGE_RESTORE_FINISHED"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE: + return "TRANSPORT_ERROR_KV_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD: + return "NO_FEEDER_THREAD"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_AGENT_ERROR: + return "FULL_AGENT_ERROR"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE: + return "TRANSPORT_ERROR_FULL_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE: + return "RESTORE_COMPLETE"; + case BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE: + return "START_PACKAGE_RESTORE"; + case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE: + return "AGENT_FAILURE"; default: return "UNKNOWN_ID"; } diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java index f73366b6af0c..812bf8e6be6a 100644 --- a/core/java/android/app/backup/BackupManagerMonitor.java +++ b/core/java/android/app/backup/BackupManagerMonitor.java @@ -18,6 +18,7 @@ package android.app.backup; import android.annotation.SystemApi; import android.app.backup.BackupAnnotations.OperationType; +import android.content.pm.PackageInfo; import android.os.Bundle; /** @@ -190,6 +191,56 @@ public class BackupManagerMonitor { public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; public static final int LOG_EVENT_ID_AGENT_LOGGING_RESULTS = 52; + /** @hide */ + public static final int LOG_EVENT_ID_START_SYSTEM_RESTORE = 53; + /** @hide */ + public static final int LOG_EVENT_ID_START_RESTORE_AT_INSTALL = 54; + /** A transport error happened during {@link PerformUnifiedRestoreTask#startRestore()} + @hide */ + public static final int LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE = 55; + /** Unable to get the name of the next package in the queue during a restore operation + @hide */ + public static final int LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME = 56; + /** Attempting a restore operation that is neither KV nor full + @hide */ + public static final int LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE = 57; + /** The package is part of KeyValue restore + @hide */ + public static final int LOG_EVENT_ID_KV_RESTORE = 58; + /** The package is part of Full restore + @hide */ + public static final int LOG_EVENT_ID_FULL_RESTORE = 59; + /** Unable to fetch the nest restore target in the queue + @hide */ + public static final int LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET = 60; + /** An error occurred while attempting KeyValueRestore + @hide */ + public static final int LOG_EVENT_ID_KV_AGENT_ERROR = 61; + /** Restore operation finished for the given package + @hide */ + public static final int LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED= 62; + /** A transport error happened during + * {@link PerformUnifiedRestoreTask#initiateOneRestore(PackageInfo, long)} + @hide */ + public static final int LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE = 63; + /** Unable to instantiate the feeder thread in full restore + @hide */ + public static final int LOG_EVENT_ID_NO_FEEDER_THREAD = 64; + /** An error occurred while attempting Full restore + @hide */ + public static final int LOG_EVENT_ID_FULL_AGENT_ERROR = 65; + /** A transport error happened during a full restore + @hide */ + public static final int LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE = 66; + /** Start restore operation for a given package + @hide */ + public static final int LOG_EVENT_ID_START_PACKAGE_RESTORE = 67; + /** Whole restore operation is complete + @hide */ + public static final int LOG_EVENT_ID_RESTORE_COMPLETE = 68; + /** Agent error during {@link PerformUnifiedRestoreTask#restoreFinished()} + @hide */ + public static final int LOG_EVENT_ID_AGENT_FAILURE = 69; /** * This method will be called each time something important happens on BackupManager. diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index e04bf11dad9f..bbec79d6cd47 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -394,6 +394,20 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // If we're starting a full-system restore, set up to begin widget ID remapping if (mIsSystemRestore) { AppWidgetBackupBridge.systemRestoreStarting(mUserId); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_START_SYSTEM_RESTORE, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); + } else { + //We are either performing RestoreAtInstall or Bmgr. + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_START_RESTORE_AT_INSTALL, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); } try { @@ -421,6 +435,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { mStatus = transport.startRestore(mToken, packages); if (mStatus != BackupTransport.TRANSPORT_OK) { Slog.e(TAG, "Transport error " + mStatus + "; no restore possible"); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + monitoringExtras); mStatus = BackupTransport.TRANSPORT_ERROR; executeNextState(UnifiedRestoreState.FINAL); return; @@ -528,6 +548,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { final String pkgName = (mRestoreDescription != null) ? mRestoreDescription.getPackageName() : null; if (pkgName == null) { + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + monitoringExtras); Slog.e(TAG, "Failure getting next package name"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); nextState = UnifiedRestoreState.FINAL; @@ -550,6 +576,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName); if (metaInfo == null) { + PackageInfo pkgInfo = new PackageInfo(); + pkgInfo.packageName = pkgName; + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA, + pkgInfo, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); Slog.e(TAG, "No metadata for " + pkgName); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, "Package metadata missing"); @@ -560,6 +594,13 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { try { mCurrentPackage = backupManagerService.getPackageManager().getPackageInfoAsUser( pkgName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); + } catch (NameNotFoundException e) { // Whoops, we thought we could restore this package but it // turns out not to be present. Skip it. @@ -641,12 +682,24 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } else { // Unknown restore type; ignore this package and move on Slog.e(TAG, "Unrecognized restore type " + type); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);; + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); nextState = UnifiedRestoreState.RUNNING_QUEUE; return; } } catch (Exception e) { Slog.e(TAG, "Can't get next restore target from transport; halting: " + e.getMessage()); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);; + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); nextState = UnifiedRestoreState.FINAL; return; @@ -663,6 +716,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { final String packageName = mCurrentPackage.packageName; // Validate some semantic requirements that apply in this way // only to the key/value restore API flow + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + /*monitoringExtras*/ addRestoreOperationTypeToEvent(/*extras*/null)); if (mCurrentPackage.applicationInfo.backupAgentName == null || "".equals(mCurrentPackage.applicationInfo.backupAgentName)) { if (MORE_DEBUG) { @@ -721,6 +778,11 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { ++mCount; } catch (Exception e) { Slog.e(TAG, "Error when attempting restore: " + e.toString()); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, + monitoringExtras); keyValueAgentErrorCleanup(false); executeNextState(UnifiedRestoreState.RUNNING_QUEUE); } @@ -759,6 +821,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Transport-level failure. This failure could be specific to package currently in // restore. Slog.e(TAG, "Error getting restore data for " + packageName); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); stage.close(); downloadFile.delete(); @@ -815,6 +883,11 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { new ArrayList<>(getExcludedKeysForPackage(packageName))); } catch (Exception e) { Slog.e(TAG, "Unable to call app for restore: " + packageName, e); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString()); // Clears any pending timeout messages as well. @@ -888,6 +961,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // // When finished, StreamFeederThread executes next state as appropriate on the // backup looper, and the overall unified restore task resumes + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + /*monitoringExtras*/ addRestoreOperationTypeToEvent(/*extras*/null)); try { StreamFeederThread feeder = new StreamFeederThread(); if (MORE_DEBUG) { @@ -903,6 +980,11 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // current target. We haven't asked the transport for data yet, though, // so we can do that simply by going back to running the restore queue. Slog.e(TAG, "Unable to construct pipes for stream restore!"); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); executeNextState(UnifiedRestoreState.RUNNING_QUEUE); } } @@ -927,6 +1009,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } catch (Exception e) { final String packageName = mCurrentPackage.packageName; Slog.e(TAG, "Unable to finalize restore of " + packageName); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString()); keyValueAgentErrorCleanup(true); @@ -1020,6 +1106,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // handling will deal properly with that. Slog.e(TAG, "Error " + result + " streaming restore for " + mCurrentPackage.packageName); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); status = result; } @@ -1032,6 +1124,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // but potentially recoverable; abandon this package's restore but // carry on with the next restore target. Slog.e(TAG, "Unable to route data for restore"); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_FULL_AGENT_ERROR, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, mCurrentPackage.packageName, "I/O error on pipes"); status = BackupTransport.AGENT_ERROR; @@ -1040,6 +1138,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // the sockets will wake up the engine and it will then tidy up the // remote end. Slog.e(TAG, "Transport failed during restore: " + e.getMessage()); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE, + mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); status = BackupTransport.TRANSPORT_ERROR; } finally { @@ -1213,6 +1317,13 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } Slog.i(TAG, "Restore complete."); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); + mListener.onFinished(callerLogString); } @@ -1313,6 +1424,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { @Override public void operationComplete(long unusedResult) { mOperationStorage.removeOperation(mEphemeralOpToken); + if (MORE_DEBUG) { Slog.i(TAG, "operationComplete() during restore: target=" + mCurrentPackage.packageName @@ -1341,6 +1453,11 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Okay, we're done with this package. Tidy up and go on to the next // app in the queue. int size = (int) mBackupDataName.length(); + Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null); + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED, mCurrentPackage, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size); diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index bc2326d8241d..16da846a093a 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -272,6 +272,32 @@ public class BackupManagerMonitorDumpsysUtils { case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED -> "Transport non-incremental backup required"; case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS -> "Agent logging results"; + case BackupManagerMonitor.LOG_EVENT_ID_START_SYSTEM_RESTORE -> "Start system restore"; + case BackupManagerMonitor.LOG_EVENT_ID_START_RESTORE_AT_INSTALL -> + "Start restore at install"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE -> + "Transport error during start restore"; + case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME -> + "Cannot get next package name"; + case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE -> "Unknown restore type"; + case BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE -> "KV restore"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE -> "Full restore"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET -> + "No next restore target"; + case BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR -> "KV agent error"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED -> + "Package restore finished"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE -> + "Transport error KV restore"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD -> "No feeder thread"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_AGENT_ERROR -> "Full agent error"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE -> + "Transport error full restore"; + case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE -> "Restore complete"; + case BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE -> + "Start package restore"; + case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE -> + "Agent failure"; default -> "Unknown log event ID: " + code; }; return id; -- GitLab From 3f8ff83497f421bef686a71b65c4afcf28ee9695 Mon Sep 17 00:00:00 2001 From: beatricemarch Date: Thu, 31 Aug 2023 12:53:16 +0000 Subject: [PATCH 39/95] Add a 2.5 MB size limit to the text file storing BMM Events. Test: atest CtsBackupHostTestCases, GtsBackupHostTestCases atest BackupManagerMonitorDumpsysUtilsTest, UserBackupManagerServiceTest Bug: 297163567 Change-Id: Ia1fdc0b465b960337f5e5c626460e305d96f4eb2 --- .../backup/UserBackupManagerService.java | 10 +++++ .../BackupManagerMonitorDumpsysUtils.java | 25 ++++++++++- .../BackupManagerMonitorDumpsysUtilsTest.java | 42 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 2d80af92eea5..38af61b16ef3 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -486,6 +486,13 @@ public class UserBackupManagerService { File baseStateDir, File dataDir, TransportManager transportManager) { + // check if we are past the retention period for BMM Events, + // if so delete expired events and do not print them to dumpsys + BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils = + new BackupManagerMonitorDumpsysUtils(); + if (backupManagerMonitorDumpsysUtils.deleteExpiredBMMEvents() && DEBUG){ + Slog.d(TAG, "BMM Events recorded for dumpsys have expired"); + } return new UserBackupManagerService( userId, context, @@ -4197,6 +4204,9 @@ public class UserBackupManagerService { // We have not recorded BMMEvents yet. pw.println("NO BACKUP MANAGER MONITOR EVENTS"); return; + } else if (bm.isFileLargerThanSizeLimit(events)){ + pw.println("BACKUP MANAGER MONITOR EVENTS FILE OVER SIZE LIMIT - " + + "future events will not be recorded"); } pw.println("START OF BACKUP MANAGER MONITOR EVENTS"); try (BufferedReader reader = new BufferedReader(new FileReader(events))) { diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index 16da846a093a..4b0d5a4928b6 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -57,11 +57,17 @@ public class BackupManagerMonitorDumpsysUtils { // Retention period of 60 days (in millisec) for the BMM Events. // After tha time has passed the text file containing the BMM events will be emptied private static final long LOGS_RETENTION_PERIOD_MILLISEC = 60 * TimeUnit.DAYS.toMillis(1); + // Size limit for the text file containing the BMM events + private static final long BMM_FILE_SIZE_LIMIT_BYTES = 25 * 1024 * 1000; // 2.5 MB + // We cache the value of IsAfterRetentionPeriod() to avoid unnecessary disk I/O // mIsAfterRetentionPeriodCached tracks if we have cached the value of IsAfterRetentionPeriod() private boolean mIsAfterRetentionPeriodCached = false; - // The cahched value of IsAfterRetentionPeriod() + // The cached value of IsAfterRetentionPeriod() private boolean mIsAfterRetentionPeriod; + // If isFileLargerThanSizeLimit(bmmEvents) returns true we cache the value to avoid + // unnecessary disk I/O + private boolean mIsFileLargerThanSizeLimit = false; /** * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that @@ -116,6 +122,11 @@ public class BackupManagerMonitorDumpsysUtils { recordSetUpTimestamp(); } + if(isFileLargerThanSizeLimit(bmmEvents)){ + // Do not write more events if the file is over size limit + return; + } + try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true); PrintWriter pw = new FastPrintWriter(out);) { @@ -192,6 +203,13 @@ public class BackupManagerMonitorDumpsysUtils { return fname; } + public boolean isFileLargerThanSizeLimit(File events){ + if (!mIsFileLargerThanSizeLimit) { + mIsFileLargerThanSizeLimit = events.length() > getBMMEventsFileSizeLimit(); + } + return mIsFileLargerThanSizeLimit; + } + private String timestamp() { long currentTime = System.currentTimeMillis(); Date date = new Date(currentTime); @@ -402,6 +420,11 @@ public class BackupManagerMonitorDumpsysUtils { return LOGS_RETENTION_PERIOD_MILLISEC; } + @VisibleForTesting + long getBMMEventsFileSizeLimit(){ + return BMM_FILE_SIZE_LIMIT_BYTES; + } + /** * Delete the BMM Events file after the retention period has passed. * diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java index a45b17e2e3c5..dcd531751cd7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java @@ -26,11 +26,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; +import java.io.FileWriter; public class BackupManagerMonitorDumpsysUtilsTest { private long mRetentionPeriod; private File mTempBMMEventsFile; private File mTempSetUpDateFile; + + private long mSizeLimit; private TestBackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils; @Rule public TemporaryFolder tmp = new TemporaryFolder(); @@ -38,6 +41,7 @@ public class BackupManagerMonitorDumpsysUtilsTest { @Before public void setUp() throws Exception { mRetentionPeriod = 30 * 60 * 1000; + mSizeLimit = 25 * 1024 * 1000; mTempBMMEventsFile = tmp.newFile("testbmmevents.txt"); mTempSetUpDateFile = tmp.newFile("testSetUpDate.txt"); mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils(); @@ -110,6 +114,33 @@ public class BackupManagerMonitorDumpsysUtilsTest { } + @Test + public void parseBackupManagerMonitorEventForDumpsys_fileOverSizeLimit_doNotRecordEvents() + throws Exception { + assertTrue(mTempBMMEventsFile.length() == 0); + Bundle event = createRestoreBMMEvent(); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + long fileSizeBefore = mTempBMMEventsFile.length(); + + mBackupManagerMonitorDumpsysUtils.setTestSizeLimit(0); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + long fileSizeAfter = mTempBMMEventsFile.length(); + assertTrue(mBackupManagerMonitorDumpsysUtils.isFileLargerThanSizeLimit(mTempBMMEventsFile)); + assertTrue(fileSizeBefore == fileSizeAfter); + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_fileUnderSizeLimit_recordEvents() + throws Exception { + assertTrue(mTempBMMEventsFile.length() == 0); + Bundle event = createRestoreBMMEvent(); + + mBackupManagerMonitorDumpsysUtils.setTestSizeLimit(25 * 1024 * 1000); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + assertFalse(mBackupManagerMonitorDumpsysUtils.isFileLargerThanSizeLimit(mTempBMMEventsFile)); + assertTrue(mTempBMMEventsFile.length() != 0); + } + @Test public void deleteExpiredBackupManagerMonitorEvent_eventsAreExpired_deleteEventsAndReturnTrue() throws Exception { @@ -238,15 +269,20 @@ public class BackupManagerMonitorDumpsysUtilsTest { extends BackupManagerMonitorDumpsysUtils { private long testRetentionPeriod; + private long testSizeLimit; TestBackupManagerMonitorDumpsysUtils() { super(); this.testRetentionPeriod = mRetentionPeriod; + this.testSizeLimit = mSizeLimit; } public void setTestRetentionPeriod(long testRetentionPeriod) { this.testRetentionPeriod = testRetentionPeriod; } + public void setTestSizeLimit(long testSizeLimit) { + this.testSizeLimit = testSizeLimit; + } @Override public File getBMMEventsFile() { @@ -263,5 +299,11 @@ public class BackupManagerMonitorDumpsysUtilsTest { return testRetentionPeriod; } + @Override + long getBMMEventsFileSizeLimit(){ + return testSizeLimit; + } + + } } -- GitLab From 0322c45426d574e24bc852b7da2b76506d7f4e54 Mon Sep 17 00:00:00 2001 From: dakinola Date: Tue, 19 Sep 2023 16:08:19 +0000 Subject: [PATCH 40/95] Remove record_task_content feature flag check in ContentRecorder Feature flag check is no longer needed and leads to scenarios where partial screen sharing fails despite the device being capable of it, so its better to remove this extra point of failure. Bug: 301273469 Test: manually built & smoke test Test: atest WmTests:ContentRecorderTests Change-Id: Iad8926c377a2cae7c1b08f874926d09cc46274a4 --- data/etc/services.core.protolog.json | 6 --- .../android/server/wm/ContentRecorder.java | 14 ------ .../server/wm/ContentRecorderTests.java | 47 ------------------- 3 files changed, 67 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index e46ba9abc9a6..ad0ead78f492 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3973,12 +3973,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1563836923": { - "message": "Content Recording: Unable to record task since feature is disabled %d", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "1577579529": { "message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b", "level": "ERROR", diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index b499dad53326..06448d0c1e84 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -33,7 +33,6 @@ import android.media.projection.IMediaProjectionManager; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; -import android.provider.DeviceConfig; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; import android.view.Display; @@ -47,11 +46,6 @@ import com.android.internal.protolog.common.ProtoLog; */ final class ContentRecorder implements WindowContainerListener { - /** - * The key for accessing the device config that controls if task recording is supported. - */ - @VisibleForTesting static final String KEY_RECORD_TASK_FEATURE = "record_task_content"; - /** * The display content this class is handling recording for. */ @@ -411,14 +405,6 @@ final class ContentRecorder implements WindowContainerListener { // TODO(206461622) Migrate to using the RootDisplayArea return dc; case RECORD_CONTENT_TASK: - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_RECORD_TASK_FEATURE, false)) { - handleStartRecordingFailed(); - ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Unable to record task since feature is disabled %d", - mDisplayContent.getDisplayId()); - return null; - } // Given the WindowToken of the region to record, retrieve the associated // SurfaceControl. if (tokenToRecord == null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index 3d3531e92abe..d2eb1cc0222b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -31,7 +31,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.ContentRecorder.KEY_RECORD_TASK_FEATURE; import static com.google.common.truth.Truth.assertThat; @@ -51,25 +50,21 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.platform.test.annotations.Presubmit; -import android.provider.DeviceConfig; import android.view.ContentRecordingSession; import android.view.DisplayInfo; import android.view.Gravity; import android.view.SurfaceControl; -import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.concurrent.CountDownLatch; /** * Tests for the {@link ContentRecorder} class. @@ -93,9 +88,6 @@ public class ContentRecorderTests extends WindowTestsBase { private ContentRecorder mContentRecorder; @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper; private SurfaceControl mRecordedSurface; - // Handle feature flag. - private ConfigListener mConfigListener; - private CountDownLatch mLatch; @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -133,23 +125,11 @@ public class ContentRecorderTests extends WindowTestsBase { mWaitingDisplaySession.setVirtualDisplayId(displayId); mWaitingDisplaySession.setWaitingForConsent(true); - mConfigListener = new ConfigListener(); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - mContext.getMainExecutor(), mConfigListener); - mLatch = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_RECORD_TASK_FEATURE, - "true", true); - // Skip unnecessary operations of relayout. spyOn(mWm.mWindowPlacerLocked); doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); } - @After - public void teardown() { - DeviceConfig.removeOnPropertiesChangedListener(mConfigListener); - } - @Test public void testIsCurrentlyRecording() { assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); @@ -183,24 +163,6 @@ public class ContentRecorderTests extends WindowTestsBase { assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); } - @Test - public void testUpdateRecording_task_featureDisabled() { - mLatch = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_RECORD_TASK_FEATURE, - "false", false); - mContentRecorder.setContentRecordingSession(mTaskSession); - mContentRecorder.updateRecording(); - assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); - } - - @Test - public void testUpdateRecording_task_featureEnabled() { - // Feature already enabled; don't need to again. - mContentRecorder.setContentRecordingSession(mTaskSession); - mContentRecorder.updateRecording(); - assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); - } - @Test public void testUpdateRecording_task_nullToken() { ContentRecordingSession session = mTaskSession; @@ -703,13 +665,4 @@ public class ContentRecorderTests extends WindowTestsBase { anyInt()); return mirroredSurface; } - - private class ConfigListener implements DeviceConfig.OnPropertiesChangedListener { - @Override - public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - if (mLatch != null && properties.getKeyset().contains(KEY_RECORD_TASK_FEATURE)) { - mLatch.countDown(); - } - } - } } -- GitLab From 08718c4e74e36d26051da65c80f4d61707d943c1 Mon Sep 17 00:00:00 2001 From: lpeter Date: Thu, 21 Sep 2023 10:47:58 +0000 Subject: [PATCH 41/95] Fix the command problem "adb shell pm list permissions -f" After using the command adb shell pm list permissions -f, it will break the list when dealing with a malformed APEX. We think that we should not break the list. We should print the error and continue to list all permissions. Bug: 300268371 Bug: 301211657 Test: Manual by using adb shell pm list permissions -f Change-Id: I8322900989385d360e3d0a7be66a334ffc1091cf --- .../com/android/server/pm/PackageManagerShellCommand.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index e1f010f62232..b6f4aeea3704 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4126,6 +4126,10 @@ class PackageManagerShellCommand extends ShellCommand { PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, 0); + if (ai == null) { + Slog.e(TAG, "Failed to get ApplicationInfo for package name(" + pii.packageName + ")."); + return null; + } AssetManager am = new AssetManager(); am.addAssetPath(ai.publicSourceDir); res = new Resources(am, null, null); -- GitLab From 5f101dcfef687891672834b61067387b4545e5d7 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Thu, 21 Sep 2023 16:15:59 +0000 Subject: [PATCH 42/95] Disable rotation check to enter overview If we are on the launcher on a phone we are likely to be in portrait which will fail the switchToOverview action. Bug: 300065764 Test: atest com.android.wm.shell.flicker.service.splitscreen.flicker.EnterSplitScreenFromOverviewGesturalNavLandscape Change-Id: I9542409f2c28b1996ad958d8c572e53ca7ea2537 --- .../src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index d53adc08d2ff..6b3cfaf33c05 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -151,8 +151,11 @@ object SplitScreenUtils { } snapshots[0].click() } else { + val rotationCheckEnabled = tapl.getExpectedRotationCheckEnabled() + tapl.setExpectedRotationCheckEnabled(false) // disable rotation check to enter overview val home = tapl.workspace .switchToOverview() + tapl.setExpectedRotationCheckEnabled(rotationCheckEnabled) // restore rotation checks ChangeDisplayOrientationRule.setRotation(rotation) home.currentTask .tapMenu() -- GitLab From 45f79af128335d1fb77cf37e3e1b1ab46b4282f6 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Thu, 21 Sep 2023 16:27:12 +0000 Subject: [PATCH 43/95] Update expected scenario for DismissSplitScreenByDivider In this case we don't seem to tag the SPLIT_SCREEN_EXIT cuj Bug: 300260196 Test: atest com.android.wm.shell.flicker.service.splitscreen.flicker.DismissSplitScreenByDividerGesturalNavLandscape Change-Id: I3b507f5cab638376c170255df9e93d0ca496bb06 --- .../flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt | 3 ++- .../flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt index 8cb25fe531b8..69499b9b488b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -31,7 +31,8 @@ import org.junit.runner.RunWith class DismissSplitScreenByDividerGesturalNavLandscape : DismissSplitScreenByDivider(Rotation.ROTATION_90) { - @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now + @ExpectedScenarios(["ENTIRE_TRACE"]) @Test override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt index 2539fd50d742..bd627f4babaa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -31,7 +31,7 @@ import org.junit.runner.RunWith class DismissSplitScreenByDividerGesturalNavPortrait : DismissSplitScreenByDivider(Rotation.ROTATION_0) { - // TODO(b/300260196): Not detecting this scenario right now + // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now @ExpectedScenarios(["ENTIRE_TRACE"]) @Test override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() -- GitLab From 9cd12c2a131c7cb2328f88dc8398bd1743b00672 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 20 Sep 2023 05:13:56 +0000 Subject: [PATCH 44/95] Preemptively reset split reparenting flag when entering split - Currently, this flag is not reset until we start the animation or when the stage visibilities officially change, but following a drop to initiate split and Shell actually receiving the transition, the app may launch a trampoline activity (ie. esp during create) which will trigger the task to be reparented back out of the split root (preventing split from succeeding). Instead we preemptively reset this state when we know that we are about to enter split, and rely on the existing paths to update the flag when the split is next fully shown or hidden. Bug: 292454704 Test: Drag app that launches a trampoline into split Change-Id: I17c8b7e92dfbf18a1bd1a9ca64b7ee7f3097f233 --- .../com/android/wm/shell/splitscreen/StageCoordinator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 94fa485efd5c..8efdfd6ca1e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1551,6 +1551,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim) { onSplitScreenEnter(); + // Preemptively reset the reparenting behavior if we know that we are entering, as starting + // split tasks with activity trampolines can inadvertently trigger the task to be + // reparented out of the split root mid-launch + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* setReparentLeafTaskIfRelaunch */); if (isSplitActive()) { prepareBringSplit(wct, taskInfo, startPosition, resizeAnim); } else { -- GitLab From 1c60dd442598c234a4c49c8f95888c962afdb6e2 Mon Sep 17 00:00:00 2001 From: Xin Guan Date: Wed, 13 Sep 2023 13:40:22 -0500 Subject: [PATCH 45/95] Add trace support to usagestats Adding a few tracing point to the code paths related to the user unlocking. Bug: 300285947 Test: verify it's available in the trace Change-Id: I2a1368c4022f174c115f0269ad6c100caa5e5817 --- .../server/usage/UsageStatsService.java | 25 ++++++++++++++++--- .../server/usage/UserUsageStatsService.java | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7db32a9f1ad9..f93c70388129 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -85,6 +85,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -452,7 +453,9 @@ public class UsageStatsService extends SystemService implements // Read pending reported events from disk and merge them with those stored in memory final LinkedList pendingEvents = new LinkedList<>(); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "loadPendingEvents"); loadPendingEventsLocked(userId, pendingEvents); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); final LinkedList eventsInMem = mReportedEvents.get(userId); if (eventsInMem != null) { pendingEvents.addAll(eventsInMem); @@ -967,6 +970,12 @@ public class UsageStatsService extends SystemService implements mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); return; } + + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + final String traceTag = "usageStatsQueueEvent(" + userId + ") #" + + UserUsageStatsService.eventToString(event.mEventType); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, traceTag); + } synchronized (mLock) { LinkedList events = mReportedEvents.get(userId); if (events == null) { @@ -980,6 +989,7 @@ public class UsageStatsService extends SystemService implements mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL); } } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } /** @@ -1944,17 +1954,23 @@ public class UsageStatsService extends SystemService implements case MSG_FLUSH_TO_DISK: flushToDisk(); break; - case MSG_UNLOCKED_USER: + case MSG_UNLOCKED_USER: { + final int userId = msg.arg1; try { - onUserUnlocked(msg.arg1); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "usageStatsHandleUserUnlocked(" + userId + ")"); + onUserUnlocked(userId); } catch (Exception e) { - if (mUserManager.isUserUnlocked(msg.arg1)) { + if (mUserManager.isUserUnlocked(userId)) { throw e; // rethrow exception - user is unlocked } else { Slog.w(TAG, "Attempted to unlock stopped or removed user " + msg.arg1); } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } break; + } case MSG_REMOVE_USER: onUserRemoved(msg.arg1); break; @@ -1986,7 +2002,10 @@ public class UsageStatsService extends SystemService implements break; case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: { final int userId = msg.arg1; + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "usageStatsHandleEstimatedLaunchTimesOnUser(" + userId + ")"); handleEstimatedLaunchTimesOnUserUnlock(userId); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } break; case MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED: { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 7d2e1a4ed86a..b5d8096deac0 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -1306,7 +1306,7 @@ class UserUsageStatsService { } } - private static String eventToString(int eventType) { + static String eventToString(int eventType) { switch (eventType) { case Event.NONE: return "NONE"; -- GitLab From bfbbe9fab73cccc37743c2f45ad547839fc836f2 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 20 Sep 2023 15:10:53 -0700 Subject: [PATCH 46/95] Add some tests for BubbleViewInfoTask - Test that populating icons & inflating views work - Test that exception loading shortcut icon doesn't doesn't crash Test: atest BubbleViewInfoTest Bug: 300073572 Change-Id: I0c67cf78d344334b3968f9bc03df0d72e34a1082 --- .../wm/shell/bubbles/BubbleViewInfoTask.java | 8 +- .../wm/shell/bubbles/BubbleViewInfoTest.kt | 201 ++++++++++++++++++ 2 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 66e69300f45f..03a63f5c8d6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -155,14 +155,14 @@ public class BubbleViewInfoTask extends AsyncTask(), bubblePositioner, + BubbleEducationController(context), mainExecutor) + val surfaceSynchronizer = { obj: Runnable -> obj.run() } + + bubbleController = BubbleController( + context, + shellInit, + shellCommandHandler, + shellController, + bubbleData, + surfaceSynchronizer, + FloatingContentCoordinator(), + mock(), + mock(), + windowManager, + WindowManagerShellWrapper(mainExecutor), + mock(), + mock(), + mock(), + mock(), + ShellTaskOrganizer(mainExecutor), + bubblePositioner, + mock(), + null, + null, + mainExecutor, + mock(), + mock(), + mock(), + mock(), + mock(), + mock(), + mock()) + + bubbleStackView = BubbleStackView(context, bubbleController, bubbleData, + surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor) + bubbleBarLayerView = BubbleBarLayerView(context, bubbleController) + } + + @Test + fun testPopulate() { + bubble = createBubbleWithShortcut() + val info = BubbleViewInfoTask.BubbleViewInfo.populate(context, + bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */) + assertThat(info!!).isNotNull() + + assertThat(info.imageView).isNotNull() + assertThat(info.expandedView).isNotNull() + assertThat(info.bubbleBarExpandedView).isNull() + + assertThat(info.shortcutInfo).isNotNull() + assertThat(info.appName).isNotEmpty() + assertThat(info.rawBadgeBitmap).isNotNull() + assertThat(info.dotPath).isNotNull() + assertThat(info.bubbleBitmap).isNotNull() + assertThat(info.badgeBitmap).isNotNull() + } + + @Test + fun testPopulateForBubbleBar() { + bubble = createBubbleWithShortcut() + val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, + bubbleController, bubbleBarLayerView, iconFactory, bubble, + false /* skipInflation */) + assertThat(info!!).isNotNull() + + assertThat(info.imageView).isNull() + assertThat(info.expandedView).isNull() + assertThat(info.bubbleBarExpandedView).isNotNull() + + assertThat(info.shortcutInfo).isNotNull() + assertThat(info.appName).isNotEmpty() + assertThat(info.rawBadgeBitmap).isNotNull() + assertThat(info.dotPath).isNotNull() + assertThat(info.bubbleBitmap).isNotNull() + assertThat(info.badgeBitmap).isNotNull() + } + + @Test + fun testPopulate_invalidShortcutIcon() { + bubble = createBubbleWithShortcut() + + // This eventually calls down to load the shortcut icon from the app, simulate an + // exception here if the app has an issue loading the shortcut icon; we default to + // the app icon in that case / none of the icons will be null. + val mockIconFactory = mock() + whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), + any())).doThrow(RuntimeException()) + + val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, + bubbleController, bubbleBarLayerView, iconFactory, bubble, + true /* skipInflation */) + assertThat(info).isNotNull() + + assertThat(info?.shortcutInfo).isNotNull() + assertThat(info?.appName).isNotEmpty() + assertThat(info?.rawBadgeBitmap).isNotNull() + assertThat(info?.dotPath).isNotNull() + assertThat(info?.bubbleBitmap).isNotNull() + assertThat(info?.badgeBitmap).isNotNull() + } + + private fun createBubbleWithShortcut(): Bubble { + val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build() + return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL, + "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, + mainExecutor, metadataFlagListener) + } +} \ No newline at end of file -- GitLab From b3e07d83c8f533fe5efc956f7eca34d8bc3b164a Mon Sep 17 00:00:00 2001 From: Andy Yu Date: Wed, 20 Sep 2023 16:13:59 -0700 Subject: [PATCH 47/95] Remove FrameRate Enum to allow all possible frame rate values Previously we restricted the frame rate override of game intervention to a certain set of values, which is now not scalable and not compatible to all devices. In addition, SurfaceFlinger is able to choose the proper frame rate to run at, there is no longer a need to impose such restriction to available frame rates. Bug: 296988493 Bug: 253178812 Tested on Pixel 7 Pro userdebug Test: atest GameManagerServiceTests Test: atest GameManagerTests Test: atest CtsFrameRateOverrideTestCases Change-Id: I2e2e18a9ec0d1a2fd505464a1297e9b7f9874d1c --- .../src/android/app/GameManagerTests.java | 10 --- .../server/app/GameManagerService.java | 67 ++++--------------- .../server/app/GameManagerShellCommand.java | 10 +-- 3 files changed, 18 insertions(+), 69 deletions(-) diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java index fac3a0ecdec2..d34c91ee48ba 100644 --- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java +++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java @@ -86,16 +86,6 @@ public final class GameManagerTests { GameModeInfo gameModeInfo = mGameManager.getGameModeInfo(mPackageName); assertNotNull(gameModeInfo); assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM)); - GameModeConfiguration unsupportedFpsConfig = - new GameModeConfiguration.Builder().setFpsOverride( - 70).setScalingFactor(0.5f).build(); - mGameManager.updateCustomGameModeConfiguration(mPackageName, unsupportedFpsConfig); - gameModeInfo = mGameManager.getGameModeInfo(mPackageName); - assertNotNull(gameModeInfo); - // TODO(b/243448953): update to non-zero FPS when matching is implemented - assertEquals(new GameModeConfiguration.Builder().setFpsOverride( - GameModeConfiguration.FPS_OVERRIDE_NONE).setScalingFactor(0.5f).build(), - gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM)); GameModeConfiguration supportedFpsConfig = new GameModeConfiguration.Builder().setFpsOverride( diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 80d14a21cc7e..5dd86e7abfc0 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -417,59 +417,6 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - public enum FrameRate { - FPS_DEFAULT(0), - FPS_30(30), - FPS_36(36), - FPS_40(40), - FPS_45(45), - FPS_48(48), - FPS_60(60), - FPS_72(72), - FPS_90(90), - FPS_120(120), - FPS_144(144), - FPS_INVALID(-1); - - public final int fps; - - FrameRate(int fps) { - this.fps = fps; - } - } - - // Turn the raw string to the corresponding fps int. - // Return 0 when disabling, -1 for invalid fps. - static int getFpsInt(String raw) { - // TODO(b/243448953): make sure this translates to proper values based on current display - switch (raw) { - case "30": - return FrameRate.FPS_30.fps; - case "36": - return FrameRate.FPS_36.fps; - case "40": - return FrameRate.FPS_40.fps; - case "45": - return FrameRate.FPS_45.fps; - case "48": - return FrameRate.FPS_48.fps; - case "60": - return FrameRate.FPS_60.fps; - case "72": - return FrameRate.FPS_72.fps; - case "90": - return FrameRate.FPS_90.fps; - case "120": - return FrameRate.FPS_120.fps; - case "144": - return FrameRate.FPS_144.fps; - case "disable": - case "": - return FrameRate.FPS_DEFAULT.fps; - } - return FrameRate.FPS_INVALID.fps; - } - /** * Called by games to communicate the current state to the platform. * @@ -717,7 +664,12 @@ public final class GameManagerService extends IGameManagerService.Stub { } public synchronized int getFps() { - return GameManagerService.getFpsInt(mFps); + try { + final int fpsInt = Integer.parseInt(mFps); + return fpsInt; + } catch (NumberFormatException e) { + return 0; + } } synchronized String getFpsStr() { @@ -757,7 +709,12 @@ public final class GameManagerService extends IGameManagerService.Stub { } android.app.GameModeConfiguration toPublicGameModeConfig() { - int fpsOverride = getFpsInt(mFps); + int fpsOverride; + try { + fpsOverride = Integer.parseInt(mFps); + } catch (NumberFormatException e) { + fpsOverride = 0; + } // TODO(b/243448953): match to proper value in case of display change? fpsOverride = fpsOverride > 0 ? fpsOverride : android.app.GameModeConfiguration.FPS_OVERRIDE_NONE; diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index 00ff489ee0ff..ab57c4fe837e 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -241,8 +241,10 @@ public class GameManagerShellCommand extends ShellCommand { case "--fps": if (fpsStr == null) { fpsStr = getNextArgRequired(); - if (fpsStr != null && GameManagerService.getFpsInt(fpsStr) == -1) { - pw.println("Invalid frame rate '" + fpsStr + "'"); + try { + Integer.parseInt(fpsStr); + } catch (NumberFormatException e) { + pw.println("Invalid frame rate: '" + fpsStr + "'"); return -1; } } else { @@ -375,8 +377,8 @@ public class GameManagerShellCommand extends ShellCommand { pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the"); pw.println(" specified scaling ratio."); - pw.println(" --fps [30|45|60|90|120|disable]: Set app to run at the specified fps,"); - pw.println(" if supported."); + pw.println(" --fps: Integer value to set app to run at the specified fps,"); + pw.println(" if supported. 0 to disable."); pw.println(" reset [--mode [2|3|performance|battery] --user ] "); pw.println(" Resets the game mode of the app to device configuration."); pw.println(" This should only be used to reset any override to non custom game mode"); -- GitLab From e3eb9a1a51a1eceb046cfd808f23b4d72b86270c Mon Sep 17 00:00:00 2001 From: Gustavo Pagani Date: Thu, 21 Sep 2023 18:08:22 +0000 Subject: [PATCH 48/95] Add single provider and single account screens Bug: 301206470 Test: N/A - feature not fully implemented yet Change-Id: I25f54e2bcb8228b46a8ca7a89f63753c639a26ef --- .../CredentialManager/horologist/Android.bp | 1 + .../compose/layout/BelowTimeTextPreview.kt | 26 ++++ .../horologist/compose/material/Button.kt | 143 ++++++++++++++++++ .../horologist/compose/material/Icon.kt | 129 ++++++++++++++++ .../horologist/compose/material/util/A11y.kt | 28 ++++ .../horologist/compose/tools/WearPreview.kt | 25 +++ packages/CredentialManager/shared/Android.bp | 1 + .../credentialmanager/ui/IntentParser.kt | 20 ++- .../ui/factory/CredentialEntryFactory.kt | 25 +++ .../credentialmanager/ui/ktx/IntentKtx.kt | 17 ++- .../credentialmanager/ui/model/Request.kt | 10 +- packages/CredentialManager/wear/Android.bp | 1 + .../wear/res/drawable/passkey_icon.xml | 21 +++ .../wear/res/values/strings.xml | 10 ++ .../ui/CredentialSelectorActivity.kt | 2 +- .../ui/CredentialSelectorViewModel.kt | 21 ++- .../ui/components/AccountRow.kt | 63 ++++++++ .../ui/components/DialogButtonsRow.kt | 71 +++++++++ .../ui/components/PasswordRow.kt | 62 ++++++++ .../ui/components/SignInHeader.kt | 83 ++++++++++ .../CredentialSelectorUiStateGetMapper.kt | 51 +++++++ .../ui/model/PasskeyUiModel.kt | 22 +++ .../ui/model/PasswordUiModel.kt | 19 +++ .../ui/screens/SingleAccountScreen.kt | 83 ++++++++++ .../ui/screens/SinglePasskeyScreen.kt | 82 ++++++++++ .../ui/screens/SinglePasswordScreen.kt | 79 ++++++++++ 26 files changed, 1085 insertions(+), 10 deletions(-) create mode 100644 packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt create mode 100644 packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt create mode 100644 packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt create mode 100644 packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt create mode 100644 packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt create mode 100644 packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt create mode 100644 packages/CredentialManager/wear/res/drawable/passkey_icon.xml create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt create mode 100644 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt diff --git a/packages/CredentialManager/horologist/Android.bp b/packages/CredentialManager/horologist/Android.bp index bb324bb8350d..bb255bdb1306 100644 --- a/packages/CredentialManager/horologist/Android.bp +++ b/packages/CredentialManager/horologist/Android.bp @@ -16,6 +16,7 @@ android_library { "androidx.compose.foundation_foundation", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui", + "androidx.compose.ui_ui-tooling", "androidx.navigation_navigation-compose", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-runtime-ktx", diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt new file mode 100644 index 000000000000..e6025fc0911f --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.google.android.horologist.compose.layout + +import androidx.compose.runtime.Composable +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +@OptIn(ExperimentalHorologistApi::class) +@Composable +public fun belowTimeTextPreview(): ScalingLazyColumnState { + return ScalingLazyColumnDefaults.belowTimeText().create() +} diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt new file mode 100644 index 000000000000..57e0c106b5f0 --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.google.android.horologist.compose.material + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.Dp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonColors +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.ButtonDefaults.DefaultButtonSize +import androidx.wear.compose.material.ButtonDefaults.DefaultIconSize +import androidx.wear.compose.material.ButtonDefaults.LargeButtonSize +import androidx.wear.compose.material.ButtonDefaults.LargeIconSize +import androidx.wear.compose.material.ButtonDefaults.SmallButtonSize +import androidx.wear.compose.material.ButtonDefaults.SmallIconSize +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * This component is an alternative to [Button], providing the following: + * - a convenient way of providing an icon and choosing its size from a range of sizes recommended + * by the Wear guidelines; + */ +@ExperimentalHorologistApi +@Composable +public fun Button( + imageVector: ImageVector, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + icon = imageVector, + contentDescription = contentDescription, + onClick = onClick, + modifier = modifier, + colors = colors, + buttonSize = buttonSize, + iconRtlMode = iconRtlMode, + enabled = enabled, + ) +} + +/** + * This component is an alternative to [Button], providing the following: + * - a convenient way of providing an icon and choosing its size from a range of sizes recommended + * by the Wear guidelines; + */ +@ExperimentalHorologistApi +@Composable +public fun Button( + @DrawableRes id: Int, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + icon = id, + contentDescription = contentDescription, + onClick = onClick, + modifier = modifier, + colors = colors, + buttonSize = buttonSize, + iconRtlMode = iconRtlMode, + enabled = enabled, + ) +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +internal fun Button( + icon: Any, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + onClick = onClick, + modifier = modifier.size(buttonSize.tapTargetSize), + enabled = enabled, + colors = colors, + ) { + val iconModifier = Modifier + .size(buttonSize.iconSize) + .align(Alignment.Center) + + Icon( + icon = icon, + contentDescription = contentDescription, + modifier = iconModifier, + rtlMode = iconRtlMode, + ) + } +} + +@ExperimentalHorologistApi +public sealed class ButtonSize( + public val iconSize: Dp, + public val tapTargetSize: Dp, +) { + public object Default : + ButtonSize(iconSize = DefaultIconSize, tapTargetSize = DefaultButtonSize) + + public object Large : ButtonSize(iconSize = LargeIconSize, tapTargetSize = LargeButtonSize) + public object Small : ButtonSize(iconSize = SmallIconSize, tapTargetSize = SmallButtonSize) + + /** + * Custom sizes should follow the [accessibility principles and guidance for touch targets](https://developer.android.com/training/wearables/accessibility#set-minimum). + */ + public data class Custom(val customIconSize: Dp, val customTapTargetSize: Dp) : + ButtonSize(iconSize = customIconSize, tapTargetSize = customTapTargetSize) +} + diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt new file mode 100644 index 000000000000..74e54c0ba479 --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.google.android.horologist.compose.material + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.LayoutDirection +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.LocalContentAlpha +import androidx.wear.compose.material.LocalContentColor +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * This component is an alternative to [Icon], providing the following: + * - a convenient way of setting the icon to be mirrored in RTL mode; + */ +@ExperimentalHorologistApi +@Composable +public fun Icon( + imageVector: ImageVector, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + Icon( + modifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ), + imageVector = imageVector, + contentDescription = contentDescription, + tint = tint, + ) +} + +/** + * This component is an alternative to [Icon], providing the following: + * - a convenient way of setting the icon to be mirrored in RTL mode; + */ +@ExperimentalHorologistApi +@Composable +public fun Icon( + @DrawableRes id: Int, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + + Icon( + painter = painterResource(id = id), + contentDescription = contentDescription, + modifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ), + tint = tint, + ) +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +internal fun Icon( + icon: Any, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + + val iconModifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ) + when (icon) { + is ImageVector -> { + Icon( + imageVector = icon, + modifier = iconModifier, + contentDescription = contentDescription, + tint = tint, + ) + } + + is Int -> { + Icon( + painter = painterResource(id = icon), + contentDescription = contentDescription, + modifier = iconModifier, + tint = tint, + ) + } + + else -> throw IllegalArgumentException("Type not supported.") + } +} + +@ExperimentalHorologistApi +public enum class IconRtlMode { + Default, + Mirrored, +} diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt new file mode 100644 index 000000000000..39de2e142edc --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.google.android.horologist.compose.material.util + +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * Make explicit that a conscious decision was made to mark an element as decorative, so it does not + * have associated actions or state. + * + * https://developer.android.com/jetpack/compose/accessibility#describe-visual + */ +@ExperimentalHorologistApi +public val DECORATIVE_ELEMENT_CONTENT_DESCRIPTION: String? = null diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt new file mode 100644 index 000000000000..0bfceeea916c --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.google.android.horologist.compose.tools + +import androidx.compose.ui.tooling.preview.Preview + +@Preview( + backgroundColor = 0xff000000, + showBackground = true, +) +public annotation class WearPreview diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp index ae4281e5561c..38d98a9d47f7 100644 --- a/packages/CredentialManager/shared/Android.bp +++ b/packages/CredentialManager/shared/Android.bp @@ -14,5 +14,6 @@ android_library { static_libs: [ "androidx.core_core-ktx", "androidx.credentials_credentials", + "guava", ], } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt index 6627af526dee..43d8fb3228d5 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt @@ -17,11 +17,15 @@ package com.android.credentialmanager.ui import android.content.Intent +import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.RequestInfo import com.android.credentialmanager.ui.ktx.cancelUiRequest +import com.android.credentialmanager.ui.ktx.getCredentialProviderDataList import com.android.credentialmanager.ui.ktx.requestInfo import com.android.credentialmanager.ui.mapper.toCancel import com.android.credentialmanager.ui.model.Request +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap fun Intent.parse(): Request { cancelUiRequest?.let { @@ -32,9 +36,23 @@ fun Intent.parse(): Request { RequestInfo.TYPE_CREATE -> { Request.Create } + RequestInfo.TYPE_GET -> { - Request.Get + Request.Get( + providers = ImmutableMap.copyOf( + getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } + ), + entries = ImmutableList.copyOf( + getCredentialProviderDataList.map { providerData -> + check(providerData is GetCredentialProviderData) { + "Invalid provider data type for GetCredentialRequest" + } + providerData + }.flatMap { it.credentialEntries } + ) + ) } + else -> { throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt new file mode 100644 index 000000000000..f01fdedc9137 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt @@ -0,0 +1,25 @@ +package com.android.credentialmanager.ui.factory + +import android.app.slice.Slice +import android.credentials.Credential +import androidx.credentials.PublicKeyCredential +import androidx.credentials.provider.CredentialEntry +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry + +fun fromSlice(slice: Slice): CredentialEntry? = + try { + when (slice.spec?.type) { + Credential.TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntry.fromSlice(slice)!! + + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> + PublicKeyCredentialEntry.fromSlice(slice)!! + + else -> CustomCredentialEntry.fromSlice(slice)!! + } + } catch (e: Exception) { + // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed + // password / passkey parsing attempt. + CustomCredentialEntry.fromSlice(slice) + } \ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt index a646851bf570..b9895a0ab52a 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt @@ -18,6 +18,9 @@ package com.android.credentialmanager.ui.ktx import android.content.Intent import android.credentials.ui.CancelUiRequest +import android.credentials.ui.CreateCredentialProviderData +import android.credentials.ui.GetCredentialProviderData +import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo val Intent.cancelUiRequest: CancelUiRequest? @@ -30,4 +33,16 @@ val Intent.requestInfo: RequestInfo? get() = this.extras?.getParcelable( RequestInfo.EXTRA_REQUEST_INFO, RequestInfo::class.java - ) \ No newline at end of file + ) + +val Intent.getCredentialProviderDataList: List + get() = this.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + GetCredentialProviderData::class.java + ) ?: emptyList() + +val Intent.createCredentialProviderDataList: List + get() = this.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + CreateCredentialProviderData::class.java + ) ?: emptyList() diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt index 3d835bebc06b..cffa2b2586b1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt @@ -16,6 +16,11 @@ package com.android.credentialmanager.ui.model +import android.credentials.ui.Entry +import android.credentials.ui.ProviderData +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap + /** * Represents the request made by the CredentialManager API. */ @@ -25,7 +30,10 @@ sealed class Request { val appPackageName: String? ) : Request() - data object Get : Request() + data class Get( + val providers: ImmutableMap, + val entries: ImmutableList, + ) : Request() data object Create : Request() } diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp index 36340fac1760..c0dff168969d 100644 --- a/packages/CredentialManager/wear/Android.bp +++ b/packages/CredentialManager/wear/Android.bp @@ -32,6 +32,7 @@ android_app { "androidx.compose.material_material-icons-extended", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui", + "androidx.compose.ui_ui-tooling", "androidx.core_core-ktx", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-livedata", diff --git a/packages/CredentialManager/wear/res/drawable/passkey_icon.xml b/packages/CredentialManager/wear/res/drawable/passkey_icon.xml new file mode 100644 index 000000000000..be366bf2a255 --- /dev/null +++ b/packages/CredentialManager/wear/res/drawable/passkey_icon.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml index 10ea9186ca85..109644f46b10 100644 --- a/packages/CredentialManager/wear/res/values/strings.xml +++ b/packages/CredentialManager/wear/res/values/strings.xml @@ -18,4 +18,14 @@ Credential Manager + + Use passkey? + + Use password? + + Cancel + + OK \ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt index 2c0575547162..a93fa81bb85b 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt @@ -47,7 +47,7 @@ class CredentialSelectorActivity : ComponentActivity() { // to the user. } - CredentialSelectorUiState.Get -> { + is CredentialSelectorUiState.Get -> { // TODO: b/301206470 - Implement get flow setContent { MaterialTheme { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt index e46fcae78f6a..c61bb2e0f949 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt @@ -23,6 +23,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.android.credentialmanager.ui.ktx.appLabel import com.android.credentialmanager.ui.ktx.requestInfo +import com.android.credentialmanager.ui.mapper.toGet +import com.android.credentialmanager.ui.model.PasskeyUiModel +import com.android.credentialmanager.ui.model.PasswordUiModel import com.android.credentialmanager.ui.model.Request import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -62,8 +65,8 @@ class CredentialSelectorViewModel( _uiState.value = CredentialSelectorUiState.Create } - Request.Get -> { - _uiState.value = CredentialSelectorUiState.Get + is Request.Get -> { + _uiState.value = request.toGet() } } } @@ -103,9 +106,15 @@ class CredentialSelectorViewModel( } sealed class CredentialSelectorUiState { - object Idle : CredentialSelectorUiState() - object Get : CredentialSelectorUiState() - object Create : CredentialSelectorUiState() + data object Idle : CredentialSelectorUiState() + sealed class Get : CredentialSelectorUiState() { + data class SingleProviderSinglePasskey(val passkeyUiModel: PasskeyUiModel) : Get() + data class SingleProviderSinglePassword(val passwordUiModel: PasswordUiModel) : Get() + + // TODO: b/301206470 add the remaining states + } + + data object Create : CredentialSelectorUiState() data class Cancel(val appName: String) : CredentialSelectorUiState() - object Finish : CredentialSelectorUiState() + data object Finish : CredentialSelectorUiState() } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt new file mode 100644 index 000000000000..c20ee0c22ad6 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.credentialmanager.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun AccountRow( + name: String, + email: String, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = name, + color = Color(0xFFE6FF7B), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.title2 + ) + Text( + text = email, + modifier = Modifier.padding(top = 7.dp), + color = Color(0xFFCAC5BC), + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.body1, + ) + } +} + +@WearPreview +@Composable +fun AccountRowPreview() { + AccountRow( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + ) +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt new file mode 100644 index 000000000000..5cb3c1590dfd --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.credentialmanager.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.ButtonDefaults +import com.google.android.horologist.compose.material.Button +import com.google.android.horologist.compose.tools.WearPreview +import com.android.credentialmanager.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun DialogButtonsRow( + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + modifier: Modifier = Modifier, + cancelButtonIcon: ImageVector = Icons.Default.Close, + okButtonIcon: ImageVector = Icons.Default.Check, + cancelButtonContentDescription: String = stringResource(R.string.dialog_cancel_button_cd), + okButtonContentDescription: String = stringResource(R.string.dialog_ok_button_cd), +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.Center, + ) { + Button( + imageVector = cancelButtonIcon, + contentDescription = cancelButtonContentDescription, + onClick = onCancelClick, + colors = ButtonDefaults.secondaryButtonColors(), + ) + Button( + imageVector = okButtonIcon, + contentDescription = okButtonContentDescription, + onClick = onOKClick, + modifier = Modifier.padding(start = 20.dp) + ) + } +} + +@WearPreview +@Composable +fun DialogButtonsRowPreview() { + DialogButtonsRow(onCancelClick = {}, onOKClick = {}) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt new file mode 100644 index 000000000000..97900b723bc3 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.credentialmanager.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun PasswordRow( + email: String, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = email, + color = Color(0xFFE6FF7B), + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.title2 + ) + Text( + text = "••••••••••••••", + modifier = Modifier.padding(top = 7.dp), + color = Color(0xFFCAC5BC), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.body1, + ) + } +} + +@WearPreview +@Composable +fun PasswordRowPreview() { + PasswordRow( + email = "beckett_bakery@gmail.com", + ) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt new file mode 100644 index 000000000000..956c56b2c7b1 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.credentialmanager.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.android.credentialmanager.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.material.Icon +import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION +import com.google.android.horologist.compose.tools.WearPreview + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun SignInHeader( + @DrawableRes icon: Int, + title: String, + modifier: Modifier = Modifier, +) { + SignInHeader( + iconContent = { + Icon( + id = icon, + contentDescription = DECORATIVE_ELEMENT_CONTENT_DESCRIPTION + ) + }, + title = title, + modifier = modifier, + ) +} + +@Composable +fun SignInHeader( + iconContent: @Composable ColumnScope.() -> Unit, + title: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + iconContent() + Text( + text = title, + modifier = Modifier + .padding(top = 6.dp) + .padding(horizontal = 10.dp), + style = MaterialTheme.typography.title3 + ) + } +} + +@WearPreview +@Composable +fun SignInHeaderPreview() { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title) + ) +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt new file mode 100644 index 000000000000..1fe1e5596805 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt @@ -0,0 +1,51 @@ +package com.android.credentialmanager.ui.mapper + +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry +import com.android.credentialmanager.ui.CredentialSelectorUiState +import com.android.credentialmanager.ui.factory.fromSlice +import com.android.credentialmanager.ui.model.PasswordUiModel +import com.android.credentialmanager.ui.model.Request + +fun Request.Get.toGet(): CredentialSelectorUiState.Get { + if (this.providers.isEmpty()) { + throw IllegalStateException("Invalid GetCredential request with empty list of providers.") + } + + if (this.entries.isEmpty()) { + throw IllegalStateException("Invalid GetCredential request with empty list of entries.") + } + + if (this.providers.size == 1) { + if (this.entries.size == 1) { + val slice = this.entries.first().slice + when (val credentialEntry = fromSlice(slice)) { + is PasswordCredentialEntry -> { + return CredentialSelectorUiState.Get.SingleProviderSinglePassword( + PasswordUiModel(credentialEntry.displayName.toString()) + ) + } + + is PublicKeyCredentialEntry -> { + TODO("b/301206470 - to be implemented") + } + + is CustomCredentialEntry -> { + TODO("b/301206470 - to be implemented") + } + + else -> { + throw IllegalStateException( + "Encountered unrecognized credential entry (${slice.spec?.type}) for " + + "GetCredential request with single account" + ) + } + } + } else { + TODO("b/301206470 - to be implemented") + } + } else { + TODO("b/301206470 - to be implemented") + } +} \ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt new file mode 100644 index 000000000000..a368de27867a --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * 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.credentialmanager.ui.model + +data class PasskeyUiModel( + val name: String, + val email: String, +) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt new file mode 100644 index 000000000000..514dca8244f1 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt @@ -0,0 +1,19 @@ +/* + * 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.0N + * + * 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.credentialmanager.ui.model + +data class PasswordUiModel(val name: String) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt new file mode 100644 index 000000000000..f344ad0bd22d --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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. + */ + +@file:OptIn(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.ScalingLazyListScope +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.AccountRow +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumn +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SingleAccountScreen( + headerContent: @Composable () -> Unit, + accountContent: @Composable () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, + content: ScalingLazyListScope.() -> Unit, +) { + ScalingLazyColumn( + columnState = columnState, + modifier = modifier.fillMaxSize(), + ) { + item { headerContent() } + item { accountContent() } + content() + } +} + +@WearPreview +@Composable +fun SingleAccountScreenPreview() { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title), + ) + }, + accountContent = { + AccountRow( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + modifier = Modifier.padding(top = 10.dp) + ) + }, + columnState = belowTimeTextPreview(), + ) { + item { + DialogButtonsRow( + onCancelClick = {}, + onOKClick = {}, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt new file mode 100644 index 000000000000..c8f871e46c83 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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. + */ + +@file:OptIn(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.AccountRow +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SinglePasskeyScreen( + name: String, + email: String, + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, +) { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title), + ) + }, + accountContent = { + AccountRow( + name = name, + email = email, + modifier = Modifier.padding(top = 10.dp), + ) + }, + columnState = columnState, + modifier = modifier.padding(horizontal = 10.dp) + ) { + item { + DialogButtonsRow( + onCancelClick = onCancelClick, + onOKClick = onOKClick, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} + +@WearPreview +@Composable +fun SinglePasskeyScreenPreview() { + SinglePasskeyScreen( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + onCancelClick = {}, + onOKClick = {}, + columnState = belowTimeTextPreview(), + ) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt new file mode 100644 index 000000000000..d863d3c68ceb --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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. + */ + +@file:OptIn(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.PasswordRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SinglePasswordScreen( + email: String, + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, +) { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_password_title), + ) + }, + accountContent = { + PasswordRow( + email = email, + modifier = Modifier.padding(top = 10.dp), + ) + }, + columnState = columnState, + modifier = modifier.padding(horizontal = 10.dp) + ) { + item { + DialogButtonsRow( + onCancelClick = onCancelClick, + onOKClick = onOKClick, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} + +@WearPreview +@Composable +fun SinglePasswordScreenPreview() { + SinglePasswordScreen( + email = "beckett_bakery@gmail.com", + onCancelClick = {}, + onOKClick = {}, + columnState = belowTimeTextPreview(), + ) +} + -- GitLab From 301e3ae60c9a587e74187e12908c843335ba6443 Mon Sep 17 00:00:00 2001 From: Chandru S Date: Wed, 20 Sep 2023 14:19:31 -0700 Subject: [PATCH 49/95] Ignore aod/dozing/off -> lockscreen transitions if the power manager wake up reason is not allowlisted Fixes: 301271023 Test: atest KeyguardFaceAuthInteractorTest Test: manually, 1. Disable AOD 2. Put device to sleep 3. Plug in device without moving it or touching the screen 4. Device should wake up and show the lockscreen 5. Face auth should not be triggered Change-Id: Ia9b65b4dab87afd19e233d46ee3511a8bbf4eef9 --- .../keyguard/FaceWakeUpTriggersConfig.kt | 13 ++++ .../DeviceEntryFaceAuthRepository.kt | 16 ++-- .../SystemUIKeyguardFaceAuthInteractor.kt | 19 +++++ .../keyguard/shared/model/WakeSleepReason.kt | 33 +++++++- .../systemui/log/FaceAuthenticationLogger.kt | 21 ++++- .../DeviceEntryFaceAuthRepositoryTest.kt | 19 +++-- .../KeyguardFaceAuthInteractorTest.kt | 76 +++++++++++++++++++ 7 files changed, 179 insertions(+), 18 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt index 788a66dcf281..5c57d4eaf7ca 100644 --- a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt @@ -24,6 +24,7 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.util.settings.GlobalSettings import java.io.PrintWriter import java.util.stream.Collectors @@ -38,6 +39,7 @@ constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpMana private val defaultTriggerFaceAuthOnWakeUpFrom: Set = resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet() private val triggerFaceAuthOnWakeUpFrom: Set + private val wakeSleepReasonsToTriggerFaceAuth: Set init { triggerFaceAuthOnWakeUpFrom = @@ -52,6 +54,14 @@ constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpMana } else { defaultTriggerFaceAuthOnWakeUpFrom } + wakeSleepReasonsToTriggerFaceAuth = + triggerFaceAuthOnWakeUpFrom + .map { + val enumVal = WakeSleepReason.fromPowerManagerWakeReason(it) + assert(enumVal != WakeSleepReason.OTHER) + enumVal + } + .toSet() dumpManager.registerDumpable(this) } @@ -59,6 +69,9 @@ constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpMana return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason) } + fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean = + wakeSleepReasonsToTriggerFaceAuth.contains(wakeReason) + override fun dump(pw: PrintWriter, args: Array) { pw.println("FaceWakeUpTriggers:") for (pmWakeReason in triggerFaceAuthOnWakeUpFrom) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 233acd90aee3..65cf0c9597d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.repository import android.app.StatusBarManager import android.content.Context -import android.hardware.face.FaceAuthenticateOptions import android.hardware.face.FaceManager import android.os.CancellationSignal import com.android.internal.logging.InstanceId @@ -47,6 +46,7 @@ import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker @@ -578,7 +578,12 @@ constructor( authCancellationSignal, faceAuthCallback, null, - FaceAuthenticateOptions.Builder().setUserId(currentUserId).build() + SysUiFaceAuthenticateOptions( + currentUserId, + uiEvent, + wakeReason = uiEvent.extraInfo + ) + .toFaceAuthenticateOptions() ) } } else if (canRunDetection.value) { @@ -587,7 +592,7 @@ constructor( uiEvent, "face auth gating check is false, falling back to detection." ) - detect() + detect(uiEvent) } else { faceAuthLogger.ignoredFaceAuthTrigger( uiEvent = uiEvent, @@ -602,7 +607,7 @@ constructor( } } - suspend fun detect() { + suspend fun detect(uiEvent: FaceAuthUiEvent) { if (!isDetectionSupported) { faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal) return @@ -619,7 +624,8 @@ constructor( faceManager?.detectFace( checkNotNull(detectCancellationSignal), detectionCallback, - FaceAuthenticateOptions.Builder().setUserId(currentUserId).build() + SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo) + .toFaceAuthenticateOptions() ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index f0df3a2e6a6f..665e16db8abb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context import android.hardware.biometrics.BiometricFaceConstants import com.android.keyguard.FaceAuthUiEvent +import com.android.keyguard.FaceWakeUpTriggersConfig import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.CoreStartable import com.android.systemui.R @@ -31,8 +32,10 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState @@ -40,6 +43,7 @@ import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -75,6 +79,8 @@ constructor( private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val userRepository: UserRepository, private val facePropertyRepository: FacePropertyRepository, + private val keyguardRepository: KeyguardRepository, + private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList = mutableListOf() @@ -117,8 +123,21 @@ constructor( keyguardTransitionInteractor.dozingToLockscreenTransition ) .filter { it.transitionState == TransitionState.STARTED } + .sample(keyguardRepository.wakefulness) + .filter { wakefulnessModel -> + val validWakeupReason = + faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom( + wakefulnessModel.lastWakeReason + ) + if (!validWakeupReason) { + faceAuthenticationLogger.ignoredWakeupReason(wakefulnessModel.lastWakeReason) + } + validWakeupReason + } .onEach { faceAuthenticationLogger.lockscreenBecameVisible(it) + FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED.extraInfo = + it.lastWakeReason.powerManagerWakeReason runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, fallbackToDetect = true diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt index c8a04fdbca52..3602be8e62a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt @@ -21,18 +21,37 @@ import android.os.PowerManager /** The reason we're waking up or going to sleep, such as pressing the power button. */ enum class WakeSleepReason( val isTouch: Boolean, + @PowerManager.WakeReason val powerManagerWakeReason: Int, ) { /** The physical power button was pressed to wake up or sleep the device. */ - POWER_BUTTON(isTouch = false), + POWER_BUTTON(isTouch = false, PowerManager.WAKE_REASON_POWER_BUTTON), /** The user has tapped or double tapped to wake the screen. */ - TAP(isTouch = true), + TAP(isTouch = true, PowerManager.WAKE_REASON_TAP), /** The user performed some sort of gesture to wake the screen. */ - GESTURE(isTouch = true), + GESTURE(isTouch = true, PowerManager.WAKE_REASON_GESTURE), + + /** Waking up because a wake key other than power was pressed. */ + KEY(isTouch = false, PowerManager.WAKE_REASON_WAKE_KEY), + + /** Waking up because a wake motion was performed */ + MOTION(isTouch = false, PowerManager.WAKE_REASON_WAKE_MOTION), + + /** Waking due to the lid being opened. */ + LID(isTouch = false, PowerManager.WAKE_REASON_LID), + + /** Waking the device due to unfolding of a foldable device. */ + UNFOLD(isTouch = false, PowerManager.WAKE_REASON_UNFOLD_DEVICE), + + /** Waking up due to a user performed lift gesture. */ + LIFT(isTouch = false, PowerManager.WAKE_REASON_LIFT), + + /** Waking up due to a user interacting with a biometric. */ + BIOMETRIC(isTouch = false, PowerManager.WAKE_REASON_BIOMETRIC), /** Something else happened to wake up or sleep the device. */ - OTHER(isTouch = false); + OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN); companion object { fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason { @@ -40,6 +59,12 @@ enum class WakeSleepReason( PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON PowerManager.WAKE_REASON_TAP -> TAP PowerManager.WAKE_REASON_GESTURE -> GESTURE + PowerManager.WAKE_REASON_WAKE_KEY -> KEY + PowerManager.WAKE_REASON_WAKE_MOTION -> MOTION + PowerManager.WAKE_REASON_LID -> LID + PowerManager.WAKE_REASON_UNFOLD_DEVICE -> UNFOLD + PowerManager.WAKE_REASON_LIFT -> LIFT + PowerManager.WAKE_REASON_BIOMETRIC -> BIOMETRIC else -> OTHER } } diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 8143f99c4d0a..66af36a71d38 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -5,7 +5,8 @@ import android.hardware.face.FaceSensorPropertiesInternal import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus -import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.FaceAuthLog import com.google.errorprone.annotations.CompileTimeConstant @@ -29,6 +30,18 @@ class FaceAuthenticationLogger constructor( @FaceAuthLog private val logBuffer: LogBuffer, ) { + + fun ignoredWakeupReason(lastWakeReason: WakeSleepReason) { + logBuffer.log( + TAG, + DEBUG, + { str1 = "$lastWakeReason" }, + { + "Ignoring off/aod/dozing -> Lockscreen transition " + + "because the last wake up reason is not allow-listed: $str1" + } + ) + } fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) { logBuffer.log( TAG, @@ -175,12 +188,12 @@ constructor( logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible") } - fun lockscreenBecameVisible(transitionStep: TransitionStep?) { + fun lockscreenBecameVisible(wake: WakefulnessModel?) { logBuffer.log( TAG, DEBUG, - { str1 = "$transitionStep" }, - { "Triggering face auth because lockscreen became visible due to transition: $str1" } + { str1 = "${wake?.lastWakeReason}" }, + { "Triggering face auth because lockscreen became visible due to wake reason: $str1" } ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 6b194f243b2c..3f87ee2b272a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -25,6 +25,9 @@ import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILA import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT import android.hardware.biometrics.ComponentInfoInternal import android.hardware.face.FaceAuthenticateOptions +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER import android.hardware.face.FaceManager import android.hardware.face.FaceSensorProperties import android.hardware.face.FaceSensorPropertiesInternal @@ -102,7 +105,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.atLeastOnce @@ -133,6 +135,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Captor private lateinit var detectionCallback: ArgumentCaptor + @Captor private lateinit var faceAuthenticateOptions: ArgumentCaptor @Captor private lateinit var cancellationSignal: ArgumentCaptor private lateinit var bypassStateChangedListener: @@ -412,7 +415,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() - underTest.detect() + underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) faceDetectIsCalled() detectionCallback.value.onFaceDetected(1, 1, true) @@ -421,6 +424,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(status.sensorId).isEqualTo(1) assertThat(status.userId).isEqualTo(1) assertThat(status.isStrongBiometric).isEqualTo(true) + assertThat(faceAuthenticateOptions.value.authenticateReason) + .isEqualTo(AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED) } @Test @@ -432,7 +437,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() clearInvocations(faceManager) - underTest.detect() + underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) verify(faceManager, never()) .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java)) @@ -467,6 +472,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { faceAuthenticateIsCalled() uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) + assertThat(faceAuthenticateOptions.value.authenticateReason) + .isEqualTo(AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) } @Test @@ -484,6 +491,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() + assertThat(faceAuthenticateOptions.value.authenticateReason) + .isEqualTo(AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER) } @Test @@ -1238,7 +1247,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .detectFace( cancellationSignal.capture(), detectionCallback.capture(), - eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build()) + faceAuthenticateOptions.capture(), ) } @@ -1251,7 +1260,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { cancellationSignal.capture(), authenticationCallback.capture(), isNull(), - eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build()) + faceAuthenticateOptions.capture(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 2ed9de26dfa3..0f9e4887fd78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -23,6 +23,7 @@ import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.FaceAuthUiEvent +import com.android.keyguard.FaceWakeUpTriggersConfig import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase @@ -42,17 +43,22 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -83,8 +89,10 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { private lateinit var facePropertyRepository: FakeFacePropertyRepository private lateinit var fakeDeviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + private lateinit var fakeKeyguardRepository: FakeKeyguardRepository @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig @Before fun setup() { @@ -108,6 +116,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { fakeUserRepository = FakeUserRepository() fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser)) facePropertyRepository = FakeFacePropertyRepository() + fakeKeyguardRepository = FakeKeyguardRepository() underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -144,6 +153,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { fakeDeviceEntryFingerprintAuthRepository, fakeUserRepository, facePropertyRepository, + fakeKeyguardRepository, + faceWakeUpTriggersConfig, ) } @@ -152,6 +163,18 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { testScope.runTest { underTest.start() + fakeKeyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.STARTING_TO_WAKE, + WakeSleepReason.LID, + WakeSleepReason.LID + ) + ) + whenever( + faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) + ) + .thenReturn(true) + keyguardTransitionRepository.sendTransitionStep( TransitionStep( KeyguardState.OFF, @@ -188,6 +211,18 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { testScope.runTest { underTest.start() + fakeKeyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.STARTING_TO_WAKE, + WakeSleepReason.LID, + WakeSleepReason.LID + ) + ) + whenever( + faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) + ) + .thenReturn(true) + keyguardTransitionRepository.sendTransitionStep( TransitionStep( KeyguardState.AOD, @@ -203,11 +238,52 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { ) } + @Test + fun faceAuthIsNotRequestedWhenLockscreenBecomesVisibleDueToIgnoredWakeReasons() = + testScope.runTest { + underTest.start() + + fakeKeyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.STARTING_TO_WAKE, + WakeSleepReason.LIFT, + WakeSleepReason.POWER_BUTTON + ) + ) + whenever( + faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LIFT) + ) + .thenReturn(false) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.DOZING, + KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value).isNull() + } + @Test fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() = testScope.runTest { underTest.start() + fakeKeyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.STARTING_TO_WAKE, + WakeSleepReason.LID, + WakeSleepReason.LID + ) + ) + whenever( + faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) + ) + .thenReturn(true) + keyguardTransitionRepository.sendTransitionStep( TransitionStep( KeyguardState.DOZING, -- GitLab From ccfdf94b931250a90e993207b9c000f2d55b7b67 Mon Sep 17 00:00:00 2001 From: Beverly Date: Thu, 21 Sep 2023 18:20:09 +0000 Subject: [PATCH 50/95] Always allow UDFPS touches if alt bouncer is visible QS can be mid-expansion but validly require UDFPS auth when notifications are not allowed to be shown on the lockscreen. When QS is in mid-expansion, we request/show the AlternateBouncer so the user has an opportunity to use UDFPS to see their notifications. Test: atest UdfpsControllerTest Test: manual (don't show notifications on LS, can still use UDFPS when requested to auth when pulling down the shade on LS) Fixes: 301037136 Change-Id: I5c91c4df394f8c6788077dd1ba60fe3123993d54 --- .../systemui/biometrics/UdfpsController.java | 4 +- .../biometrics/UdfpsControllerTest.java | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index c29f884c848d..3472a859ac82 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -562,7 +562,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { + mOverlay.getRequestId()); return false; } - if (mLockscreenShadeTransitionController.getQSDragProgress() != 0f + + if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f + && !mAlternateBouncerInteractor.isVisibleState()) || mPrimaryBouncerInteractor.isInTransit()) { return false; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 7dd88b437f17..e7fc870fab33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1597,6 +1597,53 @@ public class UdfpsControllerTest extends SysuiTestCase { anyBoolean()); } + + @Test + public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible() + throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultMove = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_MOVE event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // GIVEN swipe down for QS + when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false); + when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f); + + // WHEN ACTION_MOVE is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultMove); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricExecutor.runAllReady(); + moveEvent.recycle(); + + // THEN the touch is still processed + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + } + @Test public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { // GIVEN UDFPS overlay is showing -- GitLab From 2d8d591b3b9f5fb14c9d2270a8b8bb1eac9328e4 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Thu, 21 Sep 2023 19:36:17 +0000 Subject: [PATCH 51/95] Use non-null assertion instead of checkNotNull(). For consistency in code style, and always avoiding a method call regardless of optimizations. Change-Id: I2c041c8ef2e5cb8e3ca6c9d3eaaec36ebb01779b --- .../server/permission/access/permission/PermissionService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 1e052c037b3c..cee2524657dc 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -985,7 +985,7 @@ class PermissionService( ) { val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy - val appOpName = checkNotNull(AppOpsManager.permissionToOp(permissionName)) + val appOpName = AppOpsManager.permissionToOp(permissionName)!! val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } } -- GitLab From 749e083d84ca159e99d2e2c73c2bfa8d3c63bf11 Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Mon, 18 Sep 2023 19:07:16 -0400 Subject: [PATCH 52/95] Initialize isLockscreenShowing as null rather than true. We were short-circuiting after boot since we thought we'd already called setLockScreenShown(true). Fixes: 300990985 Test: reboot device with refactor flag enabled Change-Id: Icc25bd750bb5205c59813c68139c7193c5f00553 --- ...indowManagerLockscreenVisibilityManager.kt | 29 +++++++++++++++---- ...wManagerLockscreenVisibilityManagerTest.kt | 10 +++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 75677f2d9e9a..8ebcece940c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -45,8 +45,13 @@ constructor( /** * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown] * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse). + * + * This value is null if we have not yet called setLockScreenShown with any value. This will + * happen during the boot sequence, but we can't default to true here since otherwise we'll + * short-circuit on the first call to setLockScreenShown since we'll think we're already + * showing. */ - private var isLockscreenShowing = true + private var isLockscreenShowing: Boolean? = null /** * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order @@ -102,7 +107,7 @@ constructor( // The surface behind is always visible if the lockscreen is not showing, so we're already // visible. - if (visible && !isLockscreenShowing) { + if (visible && isLockscreenShowing != true) { Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing") return } @@ -159,9 +164,23 @@ constructor( } } + /** + * Sets the lockscreen state WM-side by calling ATMS#setLockScreenShown. + * + * [lockscreenShowing] defaults to true, since it's only ever null during the boot sequence, + * when we haven't yet called ATMS#setLockScreenShown. Typically, + * setWmLockscreenState(lockscreenShowing = true) is called early in the boot sequence, before + * setWmLockscreenState(aodVisible = true), so we don't expect to need to use this default, but + * if so, true should be the right choice. + */ private fun setWmLockscreenState( - lockscreenShowing: Boolean = this.isLockscreenShowing, - aodVisible: Boolean = this.isAodVisible + lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also { + Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " + + "because setAodVisible was called before the first setLockscreenShown " + + "call during boot. This is not typical, but is theoretically possible. " + + "If you're investigating the lockscreen showing unexpectedly, start here.") + }, + aodVisible: Boolean = this.isAodVisible ) { Log.d( TAG, @@ -201,6 +220,6 @@ constructor( } companion object { - private val TAG = this::class.java.simpleName + private val TAG = WindowManagerLockscreenVisibilityManager::class.java.simpleName } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 623c8771b3d2..7a17435b9165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -67,6 +67,7 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { underTest.setLockscreenShown(true) underTest.setAodVisible(true) + verify(activityTaskManagerService).setLockScreenShown(true, false) verify(activityTaskManagerService).setLockScreenShown(true, true) verifyNoMoreInteractions(activityTaskManagerService) } @@ -76,6 +77,7 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { underTest.setLockscreenShown(true) underTest.setAodVisible(true) + verify(activityTaskManagerService).setLockScreenShown(true, false) verify(activityTaskManagerService).setLockScreenShown(true, true) verifyNoMoreInteractions(activityTaskManagerService) @@ -97,4 +99,12 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { verifyNoMoreInteractions(activityTaskManagerService) } + + @Test + fun testAodVisible_noLockscreenShownCallYet_defaultsToShowLockscreen() { + underTest.setAodVisible(false) + + verify(activityTaskManagerService).setLockScreenShown(true, false) + verifyNoMoreInteractions(activityTaskManagerService) + } } -- GitLab From ac3368dd225dff58337039d4ccd1636e79719ebb Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 20 Sep 2023 18:37:34 -0400 Subject: [PATCH 53/95] [QS] Add contentDescription and stateDescription to tile These values were missing from the initial implementation. Since the states are broken out from the way the old tile defined them, they're generally simpler to see here. Mostly the content description is the same as the underlying flow's description prepended by "Internet,". Note that ethernet didn't have a content description defined in the old tile impl. It's been added here. Test: InternetTileViewModelTest Test: manually testing different connections Fixes: 299292270 Change-Id: Ieb16b594d235f8144014bc88dc3c565153661a07 --- .../shared/ui/model/InternetTileModel.kt | 11 +++++ .../ui/viewmodel/InternetTileViewModel.kt | 42 ++++++++++++++----- .../ui/viewmodel/InternetTileViewModelTest.kt | 26 ++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt index 1f076ed30f8a..18865900eef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt @@ -20,6 +20,8 @@ import android.content.Context import android.graphics.drawable.Drawable import android.service.quicksettings.Tile import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.plugins.qs.QSTile @@ -31,6 +33,8 @@ sealed interface InternetTileModel { val secondaryLabel: Text? val iconId: Int? val icon: QSTile.Icon? + val stateDescription: ContentDescription? + val contentDescription: ContentDescription? fun applyTo(state: QSTile.BooleanState, context: Context) { if (secondaryLabel != null) { @@ -39,6 +43,9 @@ sealed interface InternetTileModel { state.secondaryLabel = secondaryTitle } + state.stateDescription = stateDescription.loadContentDescription(context) + state.contentDescription = contentDescription.loadContentDescription(context) + // To support both SignalDrawable and other icons, give priority to icons over IDs if (icon != null) { state.icon = icon @@ -59,6 +66,8 @@ sealed interface InternetTileModel { override val secondaryLabel: Text? = null, override val iconId: Int? = null, override val icon: QSTile.Icon? = null, + override val stateDescription: ContentDescription? = null, + override val contentDescription: ContentDescription? = null, ) : InternetTileModel data class Inactive( @@ -66,6 +75,8 @@ sealed interface InternetTileModel { override val secondaryLabel: Text? = null, override val iconId: Int? = null, override val icon: QSTile.Icon? = null, + override val stateDescription: ContentDescription? = null, + override val contentDescription: ContentDescription? = null, ) : InternetTileModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt index b6f167704cd7..3f82c5bfc33c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import android.content.Context import com.android.systemui.R +import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -61,16 +62,21 @@ constructor( private val context: Context, @Application scope: CoroutineScope, ) { + private val internetLabel: String = context.getString(R.string.quick_settings_internet_label) + // Three symmetrical Flows that can be switched upon based on the value of // [DefaultConnectionModel] private val wifiIconFlow: Flow = wifiInteractor.wifiNetwork.flatMapLatest { val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true) if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) { + val secondary = removeDoubleQuotes(it.ssid) flowOf( InternetTileModel.Active( - secondaryTitle = removeDoubleQuotes(it.ssid), - icon = ResourceIcon.get(wifiIcon.icon.res) + secondaryTitle = secondary, + icon = ResourceIcon.get(wifiIcon.icon.res), + stateDescription = wifiIcon.contentDescription, + contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"), ) ) } else { @@ -109,10 +115,13 @@ constructor( it.signalLevelIcon, mobileDataContentName, ) { networkNameModel, signalIcon, dataContentDescription -> + val secondary = + mobileDataContentConcat(networkNameModel.name, dataContentDescription) InternetTileModel.Active( - secondaryTitle = - mobileDataContentConcat(networkNameModel.name, dataContentDescription), + secondaryTitle = secondary, icon = SignalIcon(signalIcon.toSignalDrawableState()), + stateDescription = ContentDescription.Loaded(secondary), + contentDescription = ContentDescription.Loaded(internetLabel), ) } } @@ -148,10 +157,13 @@ constructor( if (it == null) { notConnectedFlow } else { + val secondary = it.contentDescription.toString() flowOf( InternetTileModel.Active( - secondaryTitle = it.contentDescription.toString(), - iconId = it.res + secondaryTitle = secondary, + iconId = it.res, + stateDescription = null, + contentDescription = ContentDescription.Loaded(secondary), ) ) } @@ -164,16 +176,23 @@ constructor( ) { networksAvailable, isAirplaneMode -> when { isAirplaneMode -> { + val secondary = context.getString(R.string.status_bar_airplane) InternetTileModel.Inactive( - secondaryTitle = context.getString(R.string.status_bar_airplane), - icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable) + secondaryTitle = secondary, + icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable), + stateDescription = null, + contentDescription = ContentDescription.Loaded(secondary), ) } networksAvailable -> { + val secondary = + context.getString(R.string.quick_settings_networks_available) InternetTileModel.Inactive( - secondaryTitle = - context.getString(R.string.quick_settings_networks_available), + secondaryTitle = secondary, iconId = R.drawable.ic_qs_no_internet_available, + stateDescription = null, + contentDescription = + ContentDescription.Loaded("$internetLabel,$secondary") ) } else -> { @@ -206,6 +225,9 @@ constructor( InternetTileModel.Inactive( secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable), iconId = R.drawable.ic_qs_no_internet_unavailable, + stateDescription = null, + contentDescription = + ContentDescription.Resource(R.string.quick_settings_networks_unavailable), ) private fun removeDoubleQuotes(string: String?): String? { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 6624ec2ece77..87163d24bae2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer @@ -42,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepo import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -72,6 +74,8 @@ class InternetTileViewModelTest : SysuiTestCase() { private val mobileConnectionRepository = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) + private val internet = context.getString(R.string.quick_settings_internet_label) + @Before fun setUp() { mobileConnectionRepository.apply { @@ -139,6 +143,9 @@ class InternetTileViewModelTest : SysuiTestCase() { level = 4, ssid = "test ssid", ) + val wifiIcon = + WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = false) + as WifiIcon.Visible connectivityRepository.setWifiConnected() wifiRepository.setIsWifiDefault(true) @@ -149,6 +156,10 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])) assertThat(latest?.iconId).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo("$internet,test ssid") + val expectedSd = wifiIcon.contentDescription + assertThat(latest?.stateDescription).isEqualTo(expectedSd) } @Test @@ -281,6 +292,11 @@ class InternetTileViewModelTest : SysuiTestCase() { .isEqualTo(context.getString(R.string.quick_settings_networks_available)) assertThat(latest?.icon).isNull() assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available) + assertThat(latest?.stateDescription).isNull() + val expectedCd = + "$internet,${context.getString(R.string.quick_settings_networks_available)}" + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(expectedCd) } @Test @@ -300,6 +316,10 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.secondaryLabel).isNull() assertThat(latest?.icon).isInstanceOf(SignalIcon::class.java) assertThat(latest?.iconId).isNull() + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryTitle) + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(internet) } @Test @@ -315,6 +335,9 @@ class InternetTileViewModelTest : SysuiTestCase() { .isEqualTo(ethernetIcon!!.contentDescription.toString()) assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully) assertThat(latest?.icon).isNull() + assertThat(latest?.stateDescription).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryTitle) } @Test @@ -330,6 +353,9 @@ class InternetTileViewModelTest : SysuiTestCase() { .isEqualTo(ethernetIcon!!.contentDescription.toString()) assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet) assertThat(latest?.icon).isNull() + assertThat(latest?.stateDescription).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryTitle) } private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { -- GitLab From 5d66f1d15d5590f23f3f3d6b4b2c56b3f34adbdc Mon Sep 17 00:00:00 2001 From: Gustavo Pagani Date: Thu, 21 Sep 2023 21:07:58 +0000 Subject: [PATCH 54/95] Remove "ui" from package name from shared project. Bug: 301206470 Test: N/A - package name refactoring Change-Id: I05392c3fefcae340e898fbb40eb52a11b79e75ac --- .../android/credentialmanager/IntentParser.kt | 42 +++++++++++++ .../{ui => }/LogConstants.kt | 2 +- .../factory/CredentialEntryFactory.kt | 2 +- .../{ui => }/ktx/IntentKtx.kt | 2 +- .../{ui => }/ktx/PackageManagerKtx.kt | 4 +- .../RequestCancelMapper.kt} | 18 +++--- .../mapper/RequestGetMapper.kt | 22 +++++++ .../{ui => }/model/Request.kt | 2 +- .../credentialmanager/ui/IntentParser.kt | 60 ------------------- .../ui/CredentialSelectorActivity.kt | 1 - .../ui/CredentialSelectorViewModel.kt | 11 ++-- .../android/credentialmanager/ui/Screen.kt | 2 +- .../CredentialSelectorUiStateGetMapper.kt | 4 +- .../ui/model/PasswordUiModel.kt | 2 +- .../ui/screens/MainScreen.kt | 4 +- 15 files changed, 95 insertions(+), 83 deletions(-) create mode 100644 packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui => }/LogConstants.kt (94%) rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui => }/factory/CredentialEntryFactory.kt (95%) rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui => }/ktx/IntentKtx.kt (97%) rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui => }/ktx/PackageManagerKtx.kt (93%) rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui/mapper/RequestMapper.kt => mapper/RequestCancelMapper.kt} (57%) create mode 100644 packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt rename packages/CredentialManager/shared/src/com/android/credentialmanager/{ui => }/model/Request.kt (96%) delete mode 100644 packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt new file mode 100644 index 000000000000..defba8dacb7b --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt @@ -0,0 +1,42 @@ +/* + * 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.0N + * + * 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.credentialmanager + +import android.content.Intent +import android.credentials.ui.RequestInfo +import com.android.credentialmanager.ktx.requestInfo +import com.android.credentialmanager.mapper.toGet +import com.android.credentialmanager.mapper.toRequestCancel +import com.android.credentialmanager.model.Request + +fun Intent.parse(): Request { + this.toRequestCancel()?.let { return it } + + return when (requestInfo?.type) { + RequestInfo.TYPE_CREATE -> { + Request.Create + } + + RequestInfo.TYPE_GET -> { + this.toGet() + } + + else -> { + throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") + } + } +} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/LogConstants.kt similarity index 94% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/LogConstants.kt index f49bb33d2e8a..44d33ff7084a 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/LogConstants.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/LogConstants.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.credentialmanager.ui +package com.android.credentialmanager const val TAG = "CredentialSelector" diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/factory/CredentialEntryFactory.kt similarity index 95% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/factory/CredentialEntryFactory.kt index f01fdedc9137..a2c1f0388807 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/factory/CredentialEntryFactory.kt @@ -1,4 +1,4 @@ -package com.android.credentialmanager.ui.factory +package com.android.credentialmanager.factory import android.app.slice.Slice import android.credentials.Credential diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt similarity index 97% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt index b9895a0ab52a..a4c20bfabb35 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.ui.ktx +package com.android.credentialmanager.ktx import android.content.Intent import android.credentials.ui.CancelUiRequest diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PackageManagerKtx.kt similarity index 93% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PackageManagerKtx.kt index 7fa0ca918e21..5f4f298241a1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/PackageManagerKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PackageManagerKtx.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.credentialmanager.ui.ktx +package com.android.credentialmanager.ktx import android.content.pm.PackageManager import android.text.TextUtils import android.util.Log -import com.android.credentialmanager.ui.TAG +import com.android.credentialmanager.TAG fun PackageManager.appLabel(appPackageName: String): String? = try { diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt similarity index 57% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt index 89766c2ec61b..86a6d23a76a9 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/mapper/RequestMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt @@ -14,12 +14,16 @@ * limitations under the License. */ -package com.android.credentialmanager.ui.mapper +package com.android.credentialmanager.mapper -import android.credentials.ui.CancelUiRequest -import com.android.credentialmanager.ui.model.Request +import android.content.Intent +import com.android.credentialmanager.ktx.cancelUiRequest +import com.android.credentialmanager.model.Request -fun CancelUiRequest.toCancel() = Request.Cancel( - showCancellationUi = this.shouldShowCancellationUi(), - appPackageName = this.appPackageName -) +fun Intent.toRequestCancel(): Request.Cancel? = + this.cancelUiRequest?.let { cancelUiRequest -> + Request.Cancel( + showCancellationUi = cancelUiRequest.shouldShowCancellationUi(), + appPackageName = cancelUiRequest.appPackageName + ) + } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt new file mode 100644 index 000000000000..ed9d56344853 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt @@ -0,0 +1,22 @@ +package com.android.credentialmanager.mapper + +import android.content.Intent +import android.credentials.ui.GetCredentialProviderData +import com.android.credentialmanager.ktx.getCredentialProviderDataList +import com.android.credentialmanager.model.Request +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap + +fun Intent.toGet() = Request.Get( + providers = ImmutableMap.copyOf( + getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } + ), + entries = ImmutableList.copyOf( + getCredentialProviderDataList.map { providerData -> + check(providerData is GetCredentialProviderData) { + "Invalid provider data type for GetCredentialRequest" + } + providerData + }.flatMap { it.credentialEntries } + ) +) \ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt similarity index 96% rename from packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt rename to packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt index cffa2b2586b1..bc073105efe1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.ui.model +package com.android.credentialmanager.model import android.credentials.ui.Entry import android.credentials.ui.ProviderData diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt deleted file mode 100644 index 43d8fb3228d5..000000000000 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt +++ /dev/null @@ -1,60 +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.0N - * - * 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.credentialmanager.ui - -import android.content.Intent -import android.credentials.ui.GetCredentialProviderData -import android.credentials.ui.RequestInfo -import com.android.credentialmanager.ui.ktx.cancelUiRequest -import com.android.credentialmanager.ui.ktx.getCredentialProviderDataList -import com.android.credentialmanager.ui.ktx.requestInfo -import com.android.credentialmanager.ui.mapper.toCancel -import com.android.credentialmanager.ui.model.Request -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap - -fun Intent.parse(): Request { - cancelUiRequest?.let { - return it.toCancel() - } - - return when (requestInfo?.type) { - RequestInfo.TYPE_CREATE -> { - Request.Create - } - - RequestInfo.TYPE_GET -> { - Request.Get( - providers = ImmutableMap.copyOf( - getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } - ), - entries = ImmutableList.copyOf( - getCredentialProviderDataList.map { providerData -> - check(providerData is GetCredentialProviderData) { - "Invalid provider data type for GetCredentialRequest" - } - providerData - }.flatMap { it.credentialEntries } - ) - ) - } - - else -> { - throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") - } - } -} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt index a93fa81bb85b..53122ba7ccdc 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt @@ -48,7 +48,6 @@ class CredentialSelectorActivity : ComponentActivity() { } is CredentialSelectorUiState.Get -> { - // TODO: b/301206470 - Implement get flow setContent { MaterialTheme { WearApp() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt index c61bb2e0f949..d22d5d1a28a3 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt @@ -21,12 +21,15 @@ import android.content.Intent import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.android.credentialmanager.ui.ktx.appLabel -import com.android.credentialmanager.ui.ktx.requestInfo -import com.android.credentialmanager.ui.mapper.toGet +import com.android.credentialmanager.TAG +import com.android.credentialmanager.parse +import com.android.credentialmanager.ktx.appLabel +import com.android.credentialmanager.ktx.requestInfo +import com.android.credentialmanager.mapper.toGet import com.android.credentialmanager.ui.model.PasskeyUiModel import com.android.credentialmanager.ui.model.PasswordUiModel -import com.android.credentialmanager.ui.model.Request +import com.android.credentialmanager.model.Request +import com.android.credentialmanager.ui.mapper.toGet import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt index ee6ea5e57c19..7d1a49b07718 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt @@ -19,5 +19,5 @@ package com.android.credentialmanager.ui sealed class Screen( val route: String, ) { - object Main : Screen("main") + data object Main : Screen("main") } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt index 1fe1e5596805..5ceec1783c84 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt @@ -4,9 +4,9 @@ import androidx.credentials.provider.CustomCredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import com.android.credentialmanager.ui.CredentialSelectorUiState -import com.android.credentialmanager.ui.factory.fromSlice +import com.android.credentialmanager.factory.fromSlice import com.android.credentialmanager.ui.model.PasswordUiModel -import com.android.credentialmanager.ui.model.Request +import com.android.credentialmanager.model.Request fun Request.Get.toGet(): CredentialSelectorUiState.Get { if (this.providers.isEmpty()) { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt index 514dca8244f1..52bbfaa818ed 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt @@ -16,4 +16,4 @@ package com.android.credentialmanager.ui.model -data class PasswordUiModel(val name: String) +data class PasswordUiModel(val email: String) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt index 662d7108ab90..94a671efc393 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt @@ -23,7 +23,9 @@ import androidx.compose.ui.Modifier import androidx.wear.compose.material.Text @Composable -fun MainScreen(modifier: Modifier = Modifier) { +fun MainScreen( + modifier: Modifier = Modifier +) { Box(modifier = modifier, contentAlignment = Alignment.Center) { Text("This is a placeholder for the main screen.") } -- GitLab From 1d50590d1a86464321fbd40062ee7edf008d0d92 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Thu, 21 Sep 2023 21:17:59 +0000 Subject: [PATCH 55/95] [Status Bar][Wifi] Unlaunch WIFI_TRACKER_LIB_FOR_WIFI_ICON to droidfood. Bug: 300829104 Bug: 292533677 Change-Id: Ia76938f44ddd9c8e031afef34035c570aa6cad89 --- packages/SystemUI/src/com/android/systemui/flags/Flags.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index cfab00125e8c..8b549956b397 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -368,7 +368,8 @@ object Flags { @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository") // TODO(b/292533677): Tracking Bug - val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon") + val WIFI_TRACKER_LIB_FOR_WIFI_ICON = + unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true) // TODO(b/293863612): Tracking Bug @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = -- GitLab From 668e17494cd1847bdca32b11edb5adb149028295 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 14 Sep 2023 20:44:16 -0700 Subject: [PATCH 56/95] Add ViewConfigurationPerfTest Created two simple benchmark tests to measure performance of ViewConfiguration#get. Bug: 299587011 Test: atest ViewConfigurationPerfTest Change-Id: If502a4acb6bc6080af134675e610a76df59c538a --- .../view/ViewConfigurationPerfTest.java | 61 +++++++++++++++++++ core/java/android/view/ViewConfiguration.java | 14 +++++ 2 files changed, 75 insertions(+) create mode 100644 apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java new file mode 100644 index 000000000000..7a7250b9e910 --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.content.Context; + +import androidx.benchmark.BenchmarkState; +import androidx.benchmark.junit4.BenchmarkRule; +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; + +@SmallTest +public class ViewConfigurationPerfTest { + @Rule + public final BenchmarkRule mBenchmarkRule = new BenchmarkRule(); + + private final Context mContext = getInstrumentation().getTargetContext(); + + @Test + public void testGet_newViewConfiguration() { + final BenchmarkState state = mBenchmarkRule.getState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset cache so that `ViewConfiguration#get` creates a new instance. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.get(mContext); + } + } + + @Test + public void testGet_cachedViewConfiguration() { + final BenchmarkState state = mBenchmarkRule.getState(); + // Do `get` once to make sure there's something cached. + ViewConfiguration.get(mContext); + + while (state.keepRunning()) { + ViewConfiguration.get(mContext); + } + } +} diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index a3ae6cf20725..6ee674ab4ab3 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -40,6 +40,8 @@ import android.util.SparseArray; import android.util.TypedValue; import android.view.flags.Flags; +import com.android.internal.annotations.VisibleForTesting; + /** * Contains methods to standard constants used in the UI for timeouts, sizes, and distances. */ @@ -602,6 +604,18 @@ public class ViewConfiguration { return configuration; } + /** + * Removes cached ViewConfiguration instances, so that we can ensure `get` constructs a new + * ViewConfiguration instance. This is useful for testing the behavior and performance of + * creating ViewConfiguration the first time. + * + * @hide + */ + @VisibleForTesting + public static void resetCacheForTesting() { + sConfigurations.clear(); + } + /** * @return The width of the horizontal scrollbar and the height of the vertical * scrollbar in dips -- GitLab From 616806e510dc3f2b9553a00b8e3b1f4b0df71c97 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Wed, 6 Sep 2023 23:08:04 -0700 Subject: [PATCH 57/95] Rotary encoder scroll haptics in View class This change implements scroll haptics in View class for rotary encoders. The View class implementation allows to avoid regression for devices which have had rotary scroll haptics in forks of previous Android releases. The long term plan for scroll haptics is to use the ScrollFeedbackProvider in widgets. As such, this implementation will eventually be removed in future Android releases once the ScrollFeedbackProvider has been well integrated with major widgets. We have also fixed a bug in HapticScrollFeedbackProvider, which was necessary in using it for View-based rotary haptics. The bug was that it was allowing scroll-limit haptics without any non-limit scroll events. Tests have accordingly been adjusted. Bug: 299587011 Test: manual, unit tests Change-Id: Ifaeb89bd5bdc8c806c718d4aa8087cc0d2bf3ae5 --- .../view/HapticScrollFeedbackProvider.java | 25 +- core/java/android/view/View.java | 94 ++++++- core/java/android/view/ViewConfiguration.java | 37 ++- .../view/flags/scroll_feedback_flags.aconfig | 7 + core/res/res/values/config.xml | 3 + core/res/res/values/symbols.xml | 1 + .../HapticScrollFeedbackProviderTest.java | 107 ++++++-- .../android/view/RotaryScrollHapticsTest.java | 259 ++++++++++++++++++ 8 files changed, 506 insertions(+), 27 deletions(-) create mode 100644 core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java index a2f1d37c2c11..1310b0ccd3a9 100644 --- a/core/java/android/view/HapticScrollFeedbackProvider.java +++ b/core/java/android/view/HapticScrollFeedbackProvider.java @@ -47,9 +47,17 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { public @interface HapticScrollFeedbackAxis {} private static final int TICK_INTERVAL_NO_TICK = 0; + private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false; private final View mView; private final ViewConfiguration mViewConfig; + /** + * Flag to disable the logic in this class if the View-based scroll haptics implementation is + * enabled. If {@code false}, this class will continue to run despite the View's scroll + * haptics implementation being enabled. This value should be set to {@code true} when this + * class is directly used by the View class. + */ + private final boolean mDisabledIfViewPlaysScrollHaptics; // Info about the cause of the latest scroll event. @@ -63,18 +71,21 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { /** The tick interval corresponding to the current InputDevice/source/axis. */ private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK; private int mTotalScrollPixels = 0; - private boolean mCanPlayLimitFeedback = true; + private boolean mCanPlayLimitFeedback = INITIAL_END_OF_LIST_HAPTICS_ENABLED; private boolean mHapticScrollFeedbackEnabled = false; public HapticScrollFeedbackProvider(@NonNull View view) { - this(view, ViewConfiguration.get(view.getContext())); + this(view, ViewConfiguration.get(view.getContext()), + /* disabledIfViewPlaysScrollHaptics= */ true); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public HapticScrollFeedbackProvider(View view, ViewConfiguration viewConfig) { + public HapticScrollFeedbackProvider( + View view, ViewConfiguration viewConfig, boolean disabledIfViewPlaysScrollHaptics) { mView = view; mViewConfig = viewConfig; + mDisabledIfViewPlaysScrollHaptics = disabledIfViewPlaysScrollHaptics; } @Override @@ -136,13 +147,19 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private void maybeUpdateCurrentConfig(int deviceId, int source, int axis) { if (mAxis != axis || mSource != source || mDeviceId != deviceId) { + if (mDisabledIfViewPlaysScrollHaptics + && (source == InputDevice.SOURCE_ROTARY_ENCODER) + && mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) { + mHapticScrollFeedbackEnabled = false; + return; + } mSource = source; mAxis = axis; mDeviceId = deviceId; mHapticScrollFeedbackEnabled = mViewConfig.isHapticScrollFeedbackEnabled(deviceId, axis, source); - mCanPlayLimitFeedback = true; + mCanPlayLimitFeedback = INITIAL_END_OF_LIST_HAPTICS_ENABLED; mTotalScrollPixels = 0; updateTickIntervals(deviceId, source, axis); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4d53b2c6b821..55374b994cd4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -919,6 +919,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sCompatibilityDone = false; + /** @hide */ + public HapticScrollFeedbackProvider mScrollFeedbackProvider = null; + /** * Use the old (broken) way of building MeasureSpecs. */ @@ -3605,6 +3608,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER * 1 PFLAG4_TRAVERSAL_TRACING_ENABLED * 1 PFLAG4_RELAYOUT_TRACING_ENABLED + * 1 PFLAG4_ROTARY_HAPTICS_DETERMINED + * 1 PFLAG4_ROTARY_HAPTICS_ENABLED + * 1 PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT + * 1 PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT * |-------|-------|-------|-------| */ @@ -3703,6 +3710,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000; + /** Indicates if rotary scroll haptics support for the view has been determined. */ + private static final int PFLAG4_ROTARY_HAPTICS_DETERMINED = 0x100000; + + /** + * Indicates if rotary scroll haptics is enabled for this view. + * The source of truth for this info is a ViewConfiguration API; this bit only caches the value. + */ + private static final int PFLAG4_ROTARY_HAPTICS_ENABLED = 0x200000; + + /** Indicates if there has been a scroll event since the last rotary input. */ + private static final int PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT = 0x400000; + + /** + * Indicates if there has been a rotary input that may generate a scroll event. + * This flag is important so that a scroll event can be properly attributed to a rotary input. + */ + private static final int PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT = 0x800000; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -15894,6 +15919,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private boolean dispatchGenericMotionEventInternal(MotionEvent event) { + final boolean isRotaryEncoderEvent = event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER); + if (isRotaryEncoderEvent) { + // Determine and cache rotary scroll haptics support if it's not yet determined. + // Caching the support is important for two reasons: + // 1) Limits call to `ViewConfiguration#get`, which we should avoid if possible. + // 2) Limits latency from the `ViewConfiguration` API, which may be slow due to feature + // flag querying. + if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_DETERMINED) == 0) { + if (ViewConfiguration.get(mContext) + .isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) { + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_ENABLED; + } + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; + } + } + final boolean processForRotaryScrollHaptics = + isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0); + if (processForRotaryScrollHaptics) { + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; + } + //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnGenericMotionListener != null @@ -15902,7 +15949,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } - if (onGenericMotionEvent(event)) { + final boolean onGenericMotionEventResult = onGenericMotionEvent(event); + // Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually + // happens. Some views may return false from `onGenericMotionEvent` even if they have done + // scrolling, so disregard the return value when processing for scroll haptics. + if (processForRotaryScrollHaptics) { + if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) { + doRotaryProgressForScrollHaptics(event); + } else { + doRotaryLimitForScrollHaptics(event); + } + } + if (onGenericMotionEventResult) { return true; } @@ -17783,6 +17841,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private HapticScrollFeedbackProvider getScrollFeedbackProvider() { + if (mScrollFeedbackProvider == null) { + mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this, + ViewConfiguration.get(mContext), /* disabledIfViewPlaysScrollHaptics= */ false); + } + return mScrollFeedbackProvider; + } + + private void doRotaryProgressForScrollHaptics(MotionEvent rotaryEvent) { + final float axisScrollValue = rotaryEvent.getAxisValue(MotionEvent.AXIS_SCROLL); + final float verticalScrollFactor = + ViewConfiguration.get(mContext).getScaledVerticalScrollFactor(); + final int scrollAmount = -Math.round(axisScrollValue * verticalScrollFactor); + getScrollFeedbackProvider().onScrollProgress( + rotaryEvent.getDeviceId(), InputDevice.SOURCE_ROTARY_ENCODER, + MotionEvent.AXIS_SCROLL, scrollAmount); + } + + private void doRotaryLimitForScrollHaptics(MotionEvent rotaryEvent) { + final boolean isStart = rotaryEvent.getAxisValue(MotionEvent.AXIS_SCROLL) > 0; + getScrollFeedbackProvider().onScrollLimit( + rotaryEvent.getDeviceId(), InputDevice.SOURCE_ROTARY_ENCODER, + MotionEvent.AXIS_SCROLL, isStart); + } + + private void processScrollEventForRotaryEncoderHaptics() { + if ((mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT) != 0) { + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; + } + } + /** * This is called in response to an internal scroll in this view (i.e., the * view scrolled its own contents). This is typically as a result of @@ -17798,6 +17888,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifySubtreeAccessibilityStateChangedIfNeeded(); postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); + processScrollEventForRotaryEncoderHaptics(); + mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 6ee674ab4ab3..2cf5d5d63596 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -377,6 +377,7 @@ public class ViewConfiguration { private final int mSmartSelectionInitializedTimeout; private final int mSmartSelectionInitializingTimeout; private final boolean mPreferKeepClearForFocusEnabled; + private final boolean mViewBasedRotaryEncoderScrollHapticsEnabledConfig; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768915) private boolean sHasPermanentMenuKey; @@ -401,6 +402,7 @@ public class ViewConfiguration { mMaximumRotaryEncoderFlingVelocity = MAXIMUM_FLING_VELOCITY; mRotaryEncoderHapticScrollFeedbackEnabled = false; mRotaryEncoderHapticScrollFeedbackTickIntervalPixels = NO_HAPTIC_SCROLL_TICK_INTERVAL; + mViewBasedRotaryEncoderScrollHapticsEnabledConfig = false; mScrollbarSize = SCROLL_BAR_SIZE; mTouchSlop = TOUCH_SLOP; mHandwritingSlop = HANDWRITING_SLOP; @@ -577,6 +579,9 @@ public class ViewConfiguration { com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); mPreferKeepClearForFocusEnabled = res.getBoolean( com.android.internal.R.bool.config_preferKeepClearForFocus); + mViewBasedRotaryEncoderScrollHapticsEnabledConfig = + res.getBoolean( + com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled); } /** @@ -592,8 +597,7 @@ public class ViewConfiguration { public static ViewConfiguration get(@NonNull @UiContext Context context) { StrictMode.assertConfigurationContext(context, "ViewConfiguration"); - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - final int density = (int) (100.0f * metrics.density); + final int density = getDisplayDensity(context); ViewConfiguration configuration = sConfigurations.get(density); if (configuration == null) { @@ -616,6 +620,16 @@ public class ViewConfiguration { sConfigurations.clear(); } + /** + * Sets the ViewConfiguration cached instanc for a given Context for testing. + * + * @hide + */ + @VisibleForTesting + public static void setInstanceForTesting(Context context, ViewConfiguration instance) { + sConfigurations.put(getDisplayDensity(context), instance); + } + /** * @return The width of the horizontal scrollbar and the height of the vertical * scrollbar in dips @@ -1325,6 +1339,20 @@ public class ViewConfiguration { return NO_HAPTIC_SCROLL_TICK_INTERVAL; } + /** + * Checks if the View-based haptic scroll feedback implementation is enabled for + * {@link InputDevice#SOURCE_ROTARY_ENCODER}s. + * + *

If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be + * muted for rotary encoders in favor of View's scroll haptics implementation. + * + * @hide + */ + public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() { + return mViewBasedRotaryEncoderScrollHapticsEnabledConfig + && Flags.useViewBasedRotaryEncoderScrollHaptics(); + } + private static boolean isInputDeviceInfoValid(int id, int axis, int source) { InputDevice device = InputManagerGlobal.getInstance().getInputDevice(id); return device != null && device.getMotionRange(axis, source) != null; @@ -1434,4 +1462,9 @@ public class ViewConfiguration { public static int getHoverTooltipHideShortTimeout() { return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT; } + + private static final int getDisplayDensity(Context context) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + return (int) (100.0f * metrics.density); + } } diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig index 62c569152ee9..d1d871c2dbda 100644 --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig @@ -5,4 +5,11 @@ flag { name: "scroll_feedback_api" description: "Enable the scroll feedback APIs" bug: "239594271" +} + +flag { + namespace: "toolkit" + name: "use_view_based_rotary_encoder_scroll_haptics" + description: "If enabled, the rotary encoder scroll haptic implementation in the View class will be used, and the HapticScrollFeedbackProvider logic for rotary encoder haptic will be muted." + bug: "299587011" } \ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2e2ec5ba52b3..3d0af3dbde3d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6711,4 +6711,7 @@ {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} devices. --> false + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fe1144958c52..e0f128d5d416 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5235,4 +5235,5 @@ + diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java index d2af2a734330..3dfeb7f0fc05 100644 --- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java +++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.Presubmit; +import android.view.flags.FeatureFlags; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -49,6 +50,7 @@ public final class HapticScrollFeedbackProviderTest { private TestView mView; @Mock ViewConfiguration mMockViewConfig; + @Mock FeatureFlags mMockFeatureFlags; private HapticScrollFeedbackProvider mProvider; @@ -56,9 +58,52 @@ public final class HapticScrollFeedbackProviderTest { public void setUp() { mMockViewConfig = mock(ViewConfiguration.class); setHapticScrollFeedbackEnabled(true); + when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) + .thenReturn(false); mView = new TestView(InstrumentationRegistry.getContext()); - mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig); + mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, + /* disabledIfViewPlaysScrollHaptics= */ true); + } + + @Test + public void testRotaryEncoder_noFeedbackWhenViewBasedFeedbackIsEnabled() { + when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) + .thenReturn(true); + setHapticScrollTickInterval(5); + + mProvider.onScrollProgress( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, + /* deltaInPixels= */ 10); + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, + /* isStart= */ true); + + assertNoFeedback(mView); + } + + @Test + public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() { + mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, + /* disabledIfViewPlaysScrollHaptics= */ false); + when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) + .thenReturn(true); + setHapticScrollTickInterval(5); + + mProvider.onScrollProgress( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, + /* deltaInPixels= */ 10); + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, + /* isStart= */ true); + + assertFeedbackCount(mView, SCROLL_TICK, 1); + assertFeedbackCount(mView, SCROLL_ITEM_FOCUS, 1); + assertFeedbackCount(mView, SCROLL_LIMIT, 1); } @Test @@ -94,20 +139,26 @@ public final class HapticScrollFeedbackProviderTest { @Test public void testScrollLimit_start() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ true); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_stop() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test @@ -207,8 +258,6 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 60); - - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2); } @@ -225,6 +274,9 @@ public final class HapticScrollFeedbackProviderTest { @Test public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -232,11 +284,14 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ true); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ true); @@ -244,11 +299,14 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ true); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); + mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -256,11 +314,13 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_notEnabledWithZeroProgress() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -275,11 +335,13 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_enabledWithProgress() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -291,11 +353,13 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2); } @Test public void testScrollLimit_enabledWithSnap() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -310,7 +374,9 @@ public final class HapticScrollFeedbackProviderTest { } @Test - public void testScrollLimit_enabledWithDissimilarSnap() { + public void testScrollLimit_notEnabledWithDissimilarSnap() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -321,11 +387,13 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } @Test public void testScrollLimit_enabledWithDissimilarProgress() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); @@ -337,28 +405,27 @@ public final class HapticScrollFeedbackProviderTest { INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2); } @Test - public void testScrollLimit_enabledWithMotionFromDifferentDeviceId() { + public void testScrollLimit_doesNotEnabledWithMotionFromDifferentDeviceId() { + mProvider.onSnapToItem( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - mProvider.onScrollLimit( - INPUT_DEVICE_2, - InputDevice.SOURCE_ROTARY_ENCODER, - MotionEvent.AXIS_SCROLL, - /* isStart= */ false); + mProvider.onSnapToItem( + INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL); mProvider.onScrollLimit( INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, /* isStart= */ false); - assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 3); + assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1); } diff --git a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java new file mode 100644 index 000000000000..9a5c1c5112e6 --- /dev/null +++ b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java @@ -0,0 +1,259 @@ +/* + * 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 static android.view.InputDevice.SOURCE_CLASS_POINTER; +import static android.view.InputDevice.SOURCE_ROTARY_ENCODER; +import static android.view.MotionEvent.ACTION_SCROLL; +import static android.view.MotionEvent.AXIS_HSCROLL; +import static android.view.MotionEvent.AXIS_SCROLL; +import static android.view.MotionEvent.AXIS_VSCROLL; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** Test for the rotary scroll haptics implementation in the View class. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@Presubmit +public final class RotaryScrollHapticsTest { + private static final int TEST_ROTARY_DEVICE_ID = 1; + private static final int TEST_RANDOM_DEVICE_ID = 2; + + private static final float TEST_SCALED_VERTICAL_SCROLL_FACTOR = 5f; + + @Mock ViewConfiguration mMockViewConfig; + @Mock HapticScrollFeedbackProvider mMockScrollFeedbackProvider; + + private TestGenericMotionEventControllingView mView; + + @Before + public void setUp() { + mMockViewConfig = mock(ViewConfiguration.class); + mMockScrollFeedbackProvider = mock(HapticScrollFeedbackProvider.class); + + Context context = InstrumentationRegistry.getTargetContext(); + mView = new TestGenericMotionEventControllingView(context); + mView.mScrollFeedbackProvider = mMockScrollFeedbackProvider; + + ViewConfiguration.setInstanceForTesting(context, mMockViewConfig); + when(mMockViewConfig.getScaledVerticalScrollFactor()) + .thenReturn(TEST_SCALED_VERTICAL_SCROLL_FACTOR); + mockRotaryScrollHapticsEnabled(true); + } + + @After + public void tearDown() { + ViewConfiguration.resetCacheForTesting(); + } + + @Test + public void testRotaryScrollHapticsDisabled_producesNoHapticEvent() { + mockRotaryScrollHapticsEnabled(false); + + mView.configureGenericMotion(/* result= */ false, /* scroll= */ false); + mView.dispatchGenericMotionEvent(createRotaryEvent(-20)); + + mView.configureGenericMotion(/* result= */ false, /* scroll= */ true); + mView.dispatchGenericMotionEvent(createRotaryEvent(20)); + + mView.configureGenericMotion(/* result= */ true, /* scroll= */ true); + mView.dispatchGenericMotionEvent(createRotaryEvent(10)); + + mView.configureGenericMotion(/* result= */ true, /* scroll= */ false); + mView.dispatchGenericMotionEvent(createRotaryEvent(-10)); + + verifyNoScrollLimit(); + verifyNoScrollProgress(); + } + + @Test + public void testNonRotaryEncoderMotion_producesNoHapticEvent() { + mView.configureGenericMotion(/* result= */ false, /* scroll= */ false); + mView.dispatchGenericMotionEvent(createGenericPointerEvent(1, 2)); + + mView.configureGenericMotion(/* result= */ false, /* scroll= */ true); + mView.dispatchGenericMotionEvent(createGenericPointerEvent(2, 2)); + + mView.configureGenericMotion(/* result= */ true, /* scroll= */ true); + mView.dispatchGenericMotionEvent(createGenericPointerEvent(1, 3)); + + mView.configureGenericMotion(/* result= */ true, /* scroll= */ false); + mView.dispatchGenericMotionEvent(createGenericPointerEvent(-1, -2)); + + verifyNoScrollLimit(); + verifyNoScrollProgress(); + } + + @Test + public void testScrollLimit_start_genericMotionEventCallbackReturningFalse_doesScrollLimit() { + mView.configureGenericMotion(/* result= */ false, /* scroll= */ false); + + mView.dispatchGenericMotionEvent(createRotaryEvent(20)); + + verifyScrollLimit(/* isStart= */ true); + verifyNoScrollProgress(); + } + + @Test + public void testScrollLimit_start_genericMotionEventCallbackReturningTrue_doesScrollLimit() { + mView.configureGenericMotion(/* result= */ true, /* scroll= */ false); + + mView.dispatchGenericMotionEvent(createRotaryEvent(20)); + + verifyScrollLimit(/* isStart= */ true); + verifyNoScrollProgress(); + } + + @Test + public void testScrollLimit_end_genericMotionEventCallbackReturningFalse_doesScrollLimit() { + mView.configureGenericMotion(/* result= */ false, /* scroll= */ false); + + mView.dispatchGenericMotionEvent(createRotaryEvent(-20)); + + verifyScrollLimit(/* isStart= */ false); + verifyNoScrollProgress(); + } + + @Test + public void testScrollLimit_end_genericMotionEventCallbackReturningTrue_doesScrollLimit() { + mView.configureGenericMotion(/* result= */ true, /* scroll= */ false); + + mView.dispatchGenericMotionEvent(createRotaryEvent(-20)); + + verifyScrollLimit(/* isStart= */ false); + verifyNoScrollProgress(); + } + + @Test + public void testScrollProgress_genericMotionEventCallbackReturningFalse_doesScrollProgress() { + mView.configureGenericMotion(/* result= */ false, /* scroll= */ true); + + mView.dispatchGenericMotionEvent(createRotaryEvent(20)); + + verifyScrollProgress(-1 * 20 * (int) TEST_SCALED_VERTICAL_SCROLL_FACTOR); + verifyNoScrollLimit(); + } + + @Test + public void testScrollProgress_genericMotionEventCallbackReturningTrue_doesScrollProgress() { + mView.configureGenericMotion(/* result= */ true, /* scroll= */ true); + + mView.dispatchGenericMotionEvent(createRotaryEvent(-20)); + + verifyScrollProgress(-1 * -20 * (int) TEST_SCALED_VERTICAL_SCROLL_FACTOR); + verifyNoScrollLimit(); + } + + private void verifyScrollProgress(int scrollPixels) { + verify(mMockScrollFeedbackProvider).onScrollProgress( + TEST_ROTARY_DEVICE_ID, SOURCE_ROTARY_ENCODER, AXIS_SCROLL, scrollPixels); + } + + private void verifyNoScrollProgress() { + verify(mMockScrollFeedbackProvider, never()).onScrollProgress( + anyInt(), anyInt(), anyInt(), anyInt()); + } + + private void verifyScrollLimit(boolean isStart) { + verify(mMockScrollFeedbackProvider).onScrollLimit( + TEST_ROTARY_DEVICE_ID, SOURCE_ROTARY_ENCODER, AXIS_SCROLL, isStart); + } + + private void verifyNoScrollLimit() { + verify(mMockScrollFeedbackProvider, never()).onScrollLimit( + anyInt(), anyInt(), anyInt(), anyBoolean()); + } + + private void mockRotaryScrollHapticsEnabled(boolean enabled) { + when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) + .thenReturn(enabled); + } + + /** + * Test implementation for View giving control on behavior of + * {@link View#onGenericMotionEvent(MotionEvent)}. + */ + private static final class TestGenericMotionEventControllingView extends View { + private boolean mGenericMotionResult; + private boolean mScrollOnGenericMotion; + + TestGenericMotionEventControllingView(Context context) { + super(context); + } + + void configureGenericMotion(boolean result, boolean scroll) { + mGenericMotionResult = result; + mScrollOnGenericMotion = scroll; + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (mScrollOnGenericMotion) { + scrollTo(100, 200); // scroll values random (not relevant for tests). + } + return mGenericMotionResult; + } + } + + private static MotionEvent createRotaryEvent(float scroll) { + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.setAxisValue(AXIS_SCROLL, scroll); + + return createGenericMotionEvent( + TEST_ROTARY_DEVICE_ID, SOURCE_ROTARY_ENCODER, ACTION_SCROLL, coords); + } + + private static MotionEvent createGenericPointerEvent(float hScroll, float vScroll) { + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.setAxisValue(AXIS_HSCROLL, hScroll); + coords.setAxisValue(AXIS_VSCROLL, vScroll); + + return createGenericMotionEvent( + TEST_RANDOM_DEVICE_ID, SOURCE_CLASS_POINTER, ACTION_SCROLL, coords); + } + + private static MotionEvent createGenericMotionEvent( + int deviceId, int source, int action, MotionEvent.PointerCoords coords) { + MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); + props.id = 0; + + return MotionEvent.obtain( + /* downTime= */ 0, /* eventTime= */ 100, action, /* pointerCount= */ 1, + new MotionEvent.PointerProperties[] {props}, + new MotionEvent.PointerCoords[] {coords}, + /* metaState= */ 0, /* buttonState= */ 0, /* xPrecision= */ 0, /* yPrecision= */ 0, + deviceId, /* edgeFlags= */ 0, source, /* flags= */ 0); + } +} -- GitLab From 8a1dacc004932283081ad49f2558bea074bbf5bf Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Tue, 18 Jul 2023 13:50:09 +0200 Subject: [PATCH 58/95] Extract alert handling into NotificationAttentionHelper To allow more advanced handling of alerting behavior. MUST_SLEEP Test: atest FrameworksUiServicesTests Bug: 270456865 Bug: 291907312 Change-Id: Ic4a2d36d9cc79b86a6040264ac7b80b68611bd9e --- .../sysui/SystemUiSystemPropertiesFlags.java | 5 + .../NotificationAttentionHelper.java | 951 ++++++++ .../NotificationManagerPrivate.java | 28 + .../NotificationManagerService.java | 179 +- .../NotificationAttentionHelperTest.java | 1973 +++++++++++++++++ .../NotificationManagerServiceTest.java | 65 +- 6 files changed, 3141 insertions(+), 60 deletions(-) create mode 100644 services/core/java/com/android/server/notification/NotificationAttentionHelper.java create mode 100644 services/core/java/com/android/server/notification/NotificationManagerPrivate.java create mode 100644 services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 4a848f68abab..cb2d93474971 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -81,6 +81,11 @@ public class SystemUiSystemPropertiesFlags { public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag( "persist.sysui.notification.propagate_channel_updates_to_conversations"); + // TODO: b/291907312 - remove feature flags + /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */ + public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag( + "persist.debug.sysui.notification.enable_attention_helper_refactor"); + /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */ public static final Flag VISIT_RISKY_URIS = devFlag( "persist.sysui.notification.visit_risky_uris"); diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java new file mode 100644 index 000000000000..75a0cf521a1d --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static android.app.Notification.FLAG_INSISTENT; +import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; + +import android.annotation.IntDef; +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.IRingtonePlayer; +import android.net.Uri; +import android.os.Binder; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.VibrationEffect; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.server.EventLogTags; +import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * NotificationManagerService helper for handling notification attention effects: + * make noise, vibrate, or flash the LED. + * @hide + */ +public final class NotificationAttentionHelper { + static final String TAG = "NotifAttentionHelper"; + static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean( + "debug.notification.interruptiveness", false); + + private final Context mContext; + private final PackageManager mPackageManager; + private final TelephonyManager mTelephonyManager; + private final NotificationManagerPrivate mNMP; + private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; + private AccessibilityManager mAccessibilityManager; + private KeyguardManager mKeyguardManager; + private AudioManager mAudioManager; + private final LightsManager mLightsManager; + private final NotificationUsageStats mUsageStats; + private final ZenModeHelper mZenModeHelper; + + private VibratorHelper mVibratorHelper; + // The last key in this list owns the hardware. + ArrayList mLights = new ArrayList<>(); + private LogicalLight mNotificationLight; + private LogicalLight mAttentionLight; + + private final boolean mUseAttentionLight; + boolean mHasLight = true; + + private final SettingsObserver mSettingsObserver; + + private boolean mIsAutomotive; + private boolean mNotificationEffectsEnabledForAutomotive; + private boolean mDisableNotificationEffects; + private int mCallState; + private String mSoundNotificationKey; + private String mVibrateNotificationKey; + private boolean mSystemReady; + private boolean mInCallStateOffHook = false; + private boolean mScreenOn = true; + private boolean mUserPresent = false; + boolean mNotificationPulseEnabled; + private final Uri mInCallNotificationUri; + private final AudioAttributes mInCallNotificationAudioAttributes; + private final float mInCallNotificationVolume; + private Binder mCallNotificationToken = null; + + + public NotificationAttentionHelper(Context context, LightsManager lightsManager, + AccessibilityManager accessibilityManager, PackageManager packageManager, + NotificationUsageStats usageStats, + NotificationManagerPrivate notificationManagerPrivate, + ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) { + mContext = context; + mPackageManager = packageManager; + mTelephonyManager = context.getSystemService(TelephonyManager.class); + mAccessibilityManager = accessibilityManager; + mLightsManager = lightsManager; + mNMP = notificationManagerPrivate; + mUsageStats = usageStats; + mZenModeHelper = zenModeHelper; + mFlagResolver = flagResolver; + + mVibratorHelper = new VibratorHelper(context); + + mNotificationLight = mLightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); + mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + + Resources resources = context.getResources(); + mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight); + mHasLight = + resources.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed); + + // Don't start allowing notifications until the setup wizard has run once. + // After that, including subsequent boots, init with notifications turned on. + // This works on the first boot because the setup wizard will toggle this + // flag at least once and we'll go back to 0 after that. + if (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) == 0) { + mDisableNotificationEffects = true; + } + + mInCallNotificationUri = Uri.parse( + "file://" + resources.getString(R.string.config_inCallNotificationSound)); + mInCallNotificationAudioAttributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); + mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume); + + mSettingsObserver = new SettingsObserver(); + } + + public void onSystemReady() { + mSystemReady = true; + + mIsAutomotive = mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); + mNotificationEffectsEnabledForAutomotive = mContext.getResources().getBoolean( + R.bool.config_enableServerNotificationEffectsForAutomotive); + + mAudioManager = mContext.getSystemService(AudioManager.class); + mKeyguardManager = mContext.getSystemService(KeyguardManager.class); + + registerBroadcastListeners(); + } + + private void registerBroadcastListeners() { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + mTelephonyManager.listen(new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + if (mCallState == state) return; + if (DEBUG) Slog.d(TAG, "Call state changed: " + callStateToString(state)); + mCallState = state; + } + }, PhoneStateListener.LISTEN_CALL_STATE); + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(Intent.ACTION_USER_PRESENT); + mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); + + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver, + UserHandle.USER_ALL); + } + + @VisibleForTesting + /** + * Determine whether this notification should attempt to make noise, vibrate, or flash the LED + * @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | + * (polite_attenuated ? 8 : 0) | (polite_muted ? 16 : 0) + */ + int buzzBeepBlinkLocked(NotificationRecord record, Signals signals) { + if (mIsAutomotive && !mNotificationEffectsEnabledForAutomotive) { + return 0; + } + boolean buzz = false; + boolean beep = false; + boolean blink = false; + + final String key = record.getKey(); + + if (DEBUG) { + Log.d(TAG, "buzzBeepBlinkLocked " + record); + } + + // Should this notification make noise, vibe, or use the LED? + final boolean aboveThreshold = + mIsAutomotive + ? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT + : record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT; + // Remember if this notification already owns the notification channels. + boolean wasBeep = key != null && key.equals(mSoundNotificationKey); + boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey); + // These are set inside the conditional if the notification is allowed to make noise. + boolean hasValidVibrate = false; + boolean hasValidSound = false; + boolean sentAccessibilityEvent = false; + + // If the notification will appear in the status bar, it should send an accessibility event + final boolean suppressedByDnd = record.isIntercepted() + && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0; + if (!record.isUpdate + && record.getImportance() > IMPORTANCE_MIN + && !suppressedByDnd + && isNotificationForCurrentUser(record, signals)) { + sendAccessibilityEvent(record); + sentAccessibilityEvent = true; + } + + if (aboveThreshold && isNotificationForCurrentUser(record, signals)) { + if (mSystemReady && mAudioManager != null) { + Uri soundUri = record.getSound(); + hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); + VibrationEffect vibration = record.getVibration(); + // Demote sound to vibration if vibration missing & phone in vibration mode. + if (vibration == null + && hasValidSound + && (mAudioManager.getRingerModeInternal() + == AudioManager.RINGER_MODE_VIBRATE) + && mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) { + boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0; + vibration = mVibratorHelper.createFallbackVibration(insistent); + } + hasValidVibrate = vibration != null; + boolean hasAudibleAlert = hasValidSound || hasValidVibrate; + if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) { + if (!sentAccessibilityEvent) { + sendAccessibilityEvent(record); + sentAccessibilityEvent = true; + } + if (DEBUG) Slog.v(TAG, "Interrupting!"); + boolean isInsistentUpdate = isInsistentUpdate(record); + if (hasValidSound) { + if (isInsistentUpdate) { + // don't reset insistent sound, it's jarring + beep = true; + } else { + if (isInCall()) { + playInCallNotification(); + beep = true; + } else { + beep = playSound(record, soundUri); + } + if (beep) { + mSoundNotificationKey = key; + } + } + } + + final boolean ringerModeSilent = + mAudioManager.getRingerModeInternal() + == AudioManager.RINGER_MODE_SILENT; + if (!isInCall() && hasValidVibrate && !ringerModeSilent) { + if (isInsistentUpdate) { + buzz = true; + } else { + buzz = playVibration(record, vibration, hasValidSound); + if (buzz) { + mVibrateNotificationKey = key; + } + } + } + + // Try to start flash notification event whenever an audible and non-suppressed + // notification is received + mAccessibilityManager.startFlashNotificationEvent(mContext, + AccessibilityManager.FLASH_REASON_NOTIFICATION, + record.getSbn().getPackageName()); + + } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) { + hasValidSound = false; + } + } + } + // If a notification is updated to remove the actively playing sound or vibrate, + // cancel that feedback now + if (wasBeep && !hasValidSound) { + clearSoundLocked(); + } + if (wasBuzz && !hasValidVibrate) { + clearVibrateLocked(); + } + + // light + // release the light + boolean wasShowLights = mLights.remove(key); + if (canShowLightsLocked(record, signals, aboveThreshold)) { + mLights.add(key); + updateLightsLocked(); + if (mUseAttentionLight && mAttentionLight != null) { + mAttentionLight.pulse(); + } + blink = true; + } else if (wasShowLights) { + updateLightsLocked(); + } + final int buzzBeepBlink = + (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0); + if (buzzBeepBlink > 0) { + // Ignore summary updates because we don't display most of the information. + if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) { + if (DEBUG_INTERRUPTIVENESS) { + Slog.v(TAG, "INTERRUPTIVENESS: " + + record.getKey() + " is not interruptive: summary"); + } + } else if (record.canBubble()) { + if (DEBUG_INTERRUPTIVENESS) { + Slog.v(TAG, "INTERRUPTIVENESS: " + + record.getKey() + " is not interruptive: bubble"); + } + } else { + record.setInterruptive(true); + if (DEBUG_INTERRUPTIVENESS) { + Slog.v(TAG, "INTERRUPTIVENESS: " + + record.getKey() + " is interruptive: alerted"); + } + } + MetricsLogger.action(record.getLogMaker() + .setCategory(MetricsEvent.NOTIFICATION_ALERT) + .setType(MetricsEvent.TYPE_OPEN) + .setSubtype(buzzBeepBlink)); + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); + } + record.setAudiblyAlerted(buzz || beep); + + return buzzBeepBlink; + } + + boolean isInsistentUpdate(final NotificationRecord record) { + return (Objects.equals(record.getKey(), mSoundNotificationKey) + || Objects.equals(record.getKey(), mVibrateNotificationKey)) + && isCurrentlyInsistent(); + } + + boolean isCurrentlyInsistent() { + return isLoopingRingtoneNotification(mNMP.getNotificationByKey(mSoundNotificationKey)) + || isLoopingRingtoneNotification( + mNMP.getNotificationByKey(mVibrateNotificationKey)); + } + + boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) { + // Suppressed because it's a silent update + final Notification notification = record.getNotification(); + if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) { + return true; + } + + // Suppressed because a user manually unsnoozed something (or similar) + if (record.shouldPostSilently()) { + return true; + } + + // muted by listener + final String disableEffects = disableNotificationEffects(record, signals.listenerHints); + if (disableEffects != null) { + ZenLog.traceDisableEffects(record, disableEffects); + return true; + } + + // suppressed due to DND + if (record.isIntercepted()) { + return true; + } + + // Suppressed because another notification in its group handles alerting + if (record.getSbn().isGroup()) { + if (notification.suppressAlertingDueToGrouping()) { + return true; + } + } + + // Suppressed for being too recently noisy + final String pkg = record.getSbn().getPackageName(); + if (mUsageStats.isAlertRateLimited(pkg)) { + Slog.e(TAG, "Muting recently noisy " + record.getKey()); + return true; + } + + // A different looping ringtone, such as an incoming call is playing + if (isCurrentlyInsistent() && !isInsistentUpdate(record)) { + return true; + } + + // Suppressed since it's a non-interruptive update to a bubble-suppressed notification + final boolean isBubbleOrOverflowed = record.canBubble() && (record.isFlagBubbleRemoved() + || record.getNotification().isBubbleNotification()); + if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed + && record.getNotification().getBubbleMetadata() != null) { + if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) { + return true; + } + } + + return false; + } + + private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) { + if (playingRecord != null) { + if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE + && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) { + return true; + } + } + return false; + } + + private boolean playSound(final NotificationRecord record, Uri soundUri) { + boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0; + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + if (!mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DEBUG) { + Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + + record.getAudioAttributes()); + } + player.playAsync(soundUri, record.getSbn().getUser(), looping, + record.getAudioAttributes()); + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "Failed playSound: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, + boolean delayVibForSound) { + // Escalate privileges so we can use the vibrator even if the + // notifying app does not have the VIBRATE permission. + final long identity = Binder.clearCallingIdentity(); + try { + if (delayVibForSound) { + new Thread(() -> { + // delay the vibration by the same amount as the notification sound + final int waitMs = mAudioManager.getFocusRampTimeMs( + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + record.getAudioAttributes()); + if (DEBUG) { + Slog.v(TAG, "Delaying vibration for notification " + + record.getKey() + " by " + waitMs + "ms"); + } + try { + Thread.sleep(waitMs); + } catch (InterruptedException e) { } + // Notifications might be canceled before it actually vibrates due to waitMs, + // so need to check that the notification is still valid for vibrate. + if (mNMP.getNotificationByKey(record.getKey()) != null) { + if (record.getKey().equals(mVibrateNotificationKey)) { + vibrate(record, effect, true); + } else { + if (DEBUG) { + Slog.v(TAG, "No vibration for notification " + + record.getKey() + ": a new notification is " + + "vibrating, or effects were cleared while waiting"); + } + } + } else { + Slog.w(TAG, "No vibration for canceled notification " + + record.getKey()); + } + }).start(); + } else { + vibrate(record, effect, false); + } + return true; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) { + // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService + // doesn't have a concept of vibrating on an app's behalf, so add the app information + // to the reason so we can still debug from bugreports + String reason = "Notification (" + record.getSbn().getOpPkg() + " " + + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); + mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason); + } + + void playInCallNotification() { + // TODO: Should we apply politeness to mInCallNotificationVolume ? + final ContentResolver cr = mContext.getContentResolver(); + if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL + && Settings.Secure.getIntForUser(cr, + Settings.Secure.IN_CALL_NOTIFICATION_ENABLED, 1, cr.getUserId()) != 0) { + new Thread() { + @Override + public void run() { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (mCallNotificationToken != null) { + player.stop(mCallNotificationToken); + } + mCallNotificationToken = new Binder(); + player.play(mCallNotificationToken, mInCallNotificationUri, + mInCallNotificationAudioAttributes, + mInCallNotificationVolume, false); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed playInCallNotification: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }.start(); + } + } + + void clearSoundLocked() { + mSoundNotificationKey = null; + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed clearSoundLocked: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + void clearVibrateLocked() { + mVibrateNotificationKey = null; + final long identity = Binder.clearCallingIdentity(); + try { + mVibratorHelper.cancelVibration(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void clearLightsLocked() { + // light + mLights.clear(); + updateLightsLocked(); + } + + public void clearEffectsLocked(String key) { + if (key.equals(mSoundNotificationKey)) { + clearSoundLocked(); + } + if (key.equals(mVibrateNotificationKey)) { + clearVibrateLocked(); + } + boolean removed = mLights.remove(key); + if (removed) { + updateLightsLocked(); + } + } + + public void clearAttentionEffects() { + clearSoundLocked(); + clearVibrateLocked(); + clearLightsLocked(); + } + + void updateLightsLocked() { + if (mNotificationLight == null) { + return; + } + + // handle notification lights + NotificationRecord ledNotification = null; + while (ledNotification == null && !mLights.isEmpty()) { + final String owner = mLights.get(mLights.size() - 1); + ledNotification = mNMP.getNotificationByKey(owner); + if (ledNotification == null) { + Slog.wtfStack(TAG, "LED Notification does not exist: " + owner); + mLights.remove(owner); + } + } + + // Don't flash while we are in a call or screen is on + if (ledNotification == null || isInCall() || mScreenOn) { + mNotificationLight.turnOff(); + } else { + NotificationRecord.Light light = ledNotification.getLight(); + if (light != null && mNotificationPulseEnabled) { + // pulse repeatedly + mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED, + light.onMs, light.offMs); + } + } + } + + boolean canShowLightsLocked(final NotificationRecord record, final Signals signals, + boolean aboveThreshold) { + // device lacks light + if (!mHasLight) { + return false; + } + // user turned lights off globally + if (!mNotificationPulseEnabled) { + return false; + } + // the notification/channel has no light + if (record.getLight() == null) { + return false; + } + // unimportant notification + if (!aboveThreshold) { + return false; + } + // suppressed due to DND + if ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) != 0) { + return false; + } + // Suppressed because it's a silent update + final Notification notification = record.getNotification(); + if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) { + return false; + } + // Suppressed because another notification in its group handles alerting + if (record.getSbn().isGroup() && record.getNotification().suppressAlertingDueToGrouping()) { + return false; + } + // not if in call + if (isInCall()) { + return false; + } + // check current user + if (!isNotificationForCurrentUser(record, signals)) { + return false; + } + // Light, but only when the screen is off + return true; + } + + private String disableNotificationEffects(NotificationRecord record, int listenerHints) { + if (mDisableNotificationEffects) { + return "booleanState"; + } + + if ((listenerHints & HINT_HOST_DISABLE_EFFECTS) != 0) { + return "listenerHints"; + } + if (record != null && record.getAudioAttributes() != null) { + if ((listenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) { + if (record.getAudioAttributes().getUsage() + != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) { + return "listenerNoti"; + } + } + if ((listenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) { + if (record.getAudioAttributes().getUsage() + == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) { + return "listenerCall"; + } + } + } + if (mCallState != TelephonyManager.CALL_STATE_IDLE && !mZenModeHelper.isCall(record)) { + return "callState"; + } + + return null; + } + + public void updateDisableNotificationEffectsLocked(int status) { + mDisableNotificationEffects = + (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; + //if (disableNotificationEffects(null) != null) { + if (mDisableNotificationEffects) { + // cancel whatever is going on + clearAttentionEffects(); + } + } + + private boolean isInCall() { + if (mInCallStateOffHook) { + return true; + } + int audioMode = mAudioManager.getMode(); + if (audioMode == AudioManager.MODE_IN_CALL + || audioMode == AudioManager.MODE_IN_COMMUNICATION) { + return true; + } + return false; + } + + private static String callStateToString(int state) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: return "CALL_STATE_IDLE"; + case TelephonyManager.CALL_STATE_RINGING: return "CALL_STATE_RINGING"; + case TelephonyManager.CALL_STATE_OFFHOOK: return "CALL_STATE_OFFHOOK"; + default: return "CALL_STATE_UNKNOWN_" + state; + } + } + + private boolean isNotificationForCurrentUser(final NotificationRecord record, + final Signals signals) { + final int currentUser; + final long token = Binder.clearCallingIdentity(); + try { + currentUser = ActivityManager.getCurrentUser(); + } finally { + Binder.restoreCallingIdentity(token); + } + return (record.getUserId() == UserHandle.USER_ALL || record.getUserId() == currentUser + || signals.isCurrentProfile); + } + + void sendAccessibilityEvent(NotificationRecord record) { + if (!mAccessibilityManager.isEnabled()) { + return; + } + + final Notification notification = record.getNotification(); + final CharSequence packageName = record.getSbn().getPackageName(); + final AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setPackageName(packageName); + event.setClassName(Notification.class.getName()); + final int visibilityOverride = record.getPackageVisibilityOverride(); + final int notifVisibility = visibilityOverride == NotificationManager.VISIBILITY_NO_OVERRIDE + ? notification.visibility : visibilityOverride; + final int userId = record.getUser().getIdentifier(); + final boolean needPublic = userId >= 0 && mKeyguardManager.isDeviceLocked(userId); + if (needPublic && notifVisibility != Notification.VISIBILITY_PUBLIC) { + // Emit the public version if we're on the lockscreen and this notification isn't + // publicly visible. + event.setParcelableData(notification.publicVersion); + } else { + event.setParcelableData(notification); + } + final CharSequence tickerText = notification.tickerText; + if (!TextUtils.isEmpty(tickerText)) { + event.getText().add(tickerText); + } + + mAccessibilityManager.sendAccessibilityEvent(event); + } + + public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { + pw.println("\n Notification attention state:"); + pw.print(prefix); + pw.println(" mSoundNotificationKey=" + mSoundNotificationKey); + pw.print(prefix); + pw.println(" mVibrateNotificationKey=" + mVibrateNotificationKey); + pw.print(prefix); + pw.println(" mDisableNotificationEffects=" + mDisableNotificationEffects); + pw.print(prefix); + pw.println(" mCallState=" + callStateToString(mCallState)); + pw.print(prefix); + pw.println(" mSystemReady=" + mSystemReady); + pw.print(prefix); + pw.println(" mNotificationPulseEnabled=" + mNotificationPulseEnabled); + + int N = mLights.size(); + if (N > 0) { + pw.print(prefix); + pw.println(" Lights List:"); + for (int i=0; i "); + } else { + pw.print(" "); + } + pw.println(mLights.get(i)); + } + pw.println(" "); + } + + } + + // External signals set from NMS + public static class Signals { + private final boolean isCurrentProfile; + private final int listenerHints; + + public Signals(boolean isCurrentProfile, int listenerHints) { + this.isCurrentProfile = isCurrentProfile; + this.listenerHints = listenerHints; + } + } + + //====================== Observers ============================= + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(Intent.ACTION_SCREEN_ON)) { + // Keep track of screen on/off state, but do not turn off the notification light + // until user passes through the lock screen or views the notification. + mScreenOn = true; + updateLightsLocked(); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + mScreenOn = false; + mUserPresent = false; + updateLightsLocked(); + } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { + mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK + .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE)); + updateLightsLocked(); + } else if (action.equals(Intent.ACTION_USER_PRESENT)) { + mUserPresent = true; + // turn off LED when user passes through lock screen + if (mNotificationLight != null) { + mNotificationLight.turnOff(); + } + } + } + }; + + private final class SettingsObserver extends ContentObserver { + + private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE); + public SettingsObserver() { + super(null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { + boolean pulseEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0, + UserHandle.USER_CURRENT) + != 0; + if (mNotificationPulseEnabled != pulseEnabled) { + mNotificationPulseEnabled = pulseEnabled; + updateLightsLocked(); + } + } + } + } + + + //TODO: cleanup most (all?) of these + //======================= FOR TESTS ===================== + @VisibleForTesting + void setIsAutomotive(boolean isAutomotive) { + mIsAutomotive = isAutomotive; + } + + @VisibleForTesting + void setNotificationEffectsEnabledForAutomotive(boolean isEnabled) { + mNotificationEffectsEnabledForAutomotive = isEnabled; + } + + @VisibleForTesting + void setSystemReady(boolean systemReady) { + mSystemReady = systemReady; + } + + @VisibleForTesting + void setKeyguardManager(KeyguardManager keyguardManager) { + mKeyguardManager = keyguardManager; + } + + @VisibleForTesting + void setAccessibilityManager(AccessibilityManager am) { + mAccessibilityManager = am; + } + + @VisibleForTesting + VibratorHelper getVibratorHelper() { + return mVibratorHelper; + } + + @VisibleForTesting + void setVibratorHelper(VibratorHelper helper) { + mVibratorHelper = helper; + } + + @VisibleForTesting + void setScreenOn(boolean on) { + mScreenOn = on; + } + + @VisibleForTesting + void setLights(LogicalLight light) { + mNotificationLight = light; + mAttentionLight = light; + mNotificationPulseEnabled = true; + mHasLight = true; + } + + @VisibleForTesting + void setAudioManager(AudioManager audioManager) { + mAudioManager = audioManager; + } + + @VisibleForTesting + void setInCallStateOffHook(boolean inCallStateOffHook) { + mInCallStateOffHook = inCallStateOffHook; + } + +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java new file mode 100644 index 000000000000..2cc63ebfc962 --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.annotation.Nullable; + +/** + * Interface that allows components (helpers) to access NotificationRecords + * without an explicit reference to NotificationManagerService. + */ +interface NotificationManagerPrivate { + @Nullable + NotificationRecord getNotificationByKey(String key); +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bada8165766c..802dfb182297 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -287,6 +287,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; @@ -315,6 +316,7 @@ import com.android.server.SystemService; import com.android.server.job.JobSchedulerInternal; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.Flags; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.notification.toast.CustomToastRecord; @@ -684,6 +686,7 @@ public class NotificationManagerService extends SystemService { private boolean mIsAutomotive; private boolean mNotificationEffectsEnabledForAutomotive; private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener; + protected NotificationAttentionHelper mAttentionHelper; private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; @@ -1167,12 +1170,16 @@ public class NotificationManagerService extends SystemService { @Override public void onSetDisabled(int status) { synchronized (mNotificationLock) { - mDisableNotificationEffects = - (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - if (disableNotificationEffects(null) != null) { - // cancel whatever's going on - clearSoundLocked(); - clearVibrateLocked(); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.updateDisableNotificationEffectsLocked(status); + } else { + mDisableNotificationEffects = + (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; + if (disableNotificationEffects(null) != null) { + // cancel whatever's going on + clearSoundLocked(); + clearVibrateLocked(); + } } } } @@ -1309,9 +1316,13 @@ public class NotificationManagerService extends SystemService { public void clearEffects() { synchronized (mNotificationLock) { if (DBG) Slog.d(TAG, "clearEffects"); - clearSoundLocked(); - clearVibrateLocked(); - clearLightsLocked(); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.clearAttentionEffects(); + } else { + clearSoundLocked(); + clearVibrateLocked(); + clearLightsLocked(); + } } } @@ -1534,7 +1545,12 @@ public class NotificationManagerService extends SystemService { int changedFlags = data.getFlags() ^ flags; if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) { // Suppress notification flag changed, clear any effects - clearEffectsLocked(key); + if (mFlagResolver.isEnabled( + NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.clearEffectsLocked(key); + } else { + clearEffectsLocked(key); + } } data.setFlags(flags); // Shouldn't alert again just because of a flag change. @@ -1626,6 +1642,12 @@ public class NotificationManagerService extends SystemService { }; + NotificationManagerPrivate mNotificationManagerPrivate = key -> { + synchronized (mNotificationLock) { + return mNotificationsByKey.get(key); + } + }; + @VisibleForTesting void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) { // If the newly visible notification has smart suggestions @@ -1873,19 +1895,28 @@ public class NotificationManagerService extends SystemService { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_ON)) { - // Keep track of screen on/off state, but do not turn off the notification light - // until user passes through the lock screen or views the notification. - mScreenOn = true; - updateNotificationPulse(); - } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - mScreenOn = false; - updateNotificationPulse(); - } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { - mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK + if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (action.equals(Intent.ACTION_SCREEN_ON)) { + // Keep track of screen on/off state, but do not turn off the notification light + // until user passes through the lock screen or views the notification. + mScreenOn = true; + updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + mScreenOn = false; + updateNotificationPulse(); + } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { + mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE)); - updateNotificationPulse(); - } else if (action.equals(Intent.ACTION_USER_STOPPED)) { + updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_USER_PRESENT)) { + // turn off LED when user passes through lock screen + if (mNotificationLight != null) { + mNotificationLight.turnOff(); + } + } + } + + if (action.equals(Intent.ACTION_USER_STOPPED)) { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userHandle >= 0) { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle, @@ -1898,11 +1929,6 @@ public class NotificationManagerService extends SystemService { REASON_PROFILE_TURNED_OFF); mSnoozeHelper.clearData(userHandle); } - } else if (action.equals(Intent.ACTION_USER_PRESENT)) { - // turn off LED when user passes through lock screen - if (mNotificationLight != null) { - mNotificationLight.turnOff(); - } } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); @@ -1976,8 +2002,10 @@ public class NotificationManagerService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(NOTIFICATION_BADGING_URI, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, + if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this, UserHandle.USER_ALL); + } resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI, @@ -2000,13 +2028,15 @@ public class NotificationManagerService extends SystemService { public void update(Uri uri) { ContentResolver resolver = getContext().getContentResolver(); - if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { - boolean pulseEnabled = Settings.System.getIntForUser(resolver, - Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) + if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { + boolean pulseEnabled = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0; - if (mNotificationPulseEnabled != pulseEnabled) { - mNotificationPulseEnabled = pulseEnabled; - updateNotificationPulse(); + if (mNotificationPulseEnabled != pulseEnabled) { + mNotificationPulseEnabled = pulseEnabled; + updateNotificationPulse(); + } } } if (uri == null || NOTIFICATION_RATE_LIMIT_URI.equals(uri)) { @@ -2491,14 +2521,22 @@ public class NotificationManagerService extends SystemService { mToastRateLimiter = toastRateLimiter; + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager, + mAccessibilityManager, mPackageManagerClient, usageStats, + mNotificationManagerPrivate, mZenModeHelper, flagResolver); + } + // register for various Intents. // If this is called within a test, make sure to unregister the intent receivers by // calling onDestroy() IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(Intent.ACTION_USER_PRESENT); + if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(Intent.ACTION_USER_PRESENT); + } filter.addAction(Intent.ACTION_USER_STOPPED); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); @@ -2818,6 +2856,9 @@ public class NotificationManagerService extends SystemService { } registerNotificationPreferencesPullers(); new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.onSystemReady(); + } } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. @@ -6375,6 +6416,9 @@ public class NotificationManagerService extends SystemService { pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate); pw.println(" hideSilentStatusBar=" + mPreferencesHelper.shouldHideSilentStatusIcons()); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.dump(pw, " ", filter); + } } pw.println(" mArchive=" + mArchive.toString()); mArchive.dumpImpl(pw, filter); @@ -7632,7 +7676,11 @@ public class NotificationManagerService extends SystemService { boolean wasPosted = removeFromNotificationListsLocked(r); cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null, SystemClock.elapsedRealtime()); - updateLightsLocked(); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.updateLightsLocked(); + } else { + updateLightsLocked(); + } if (isSnoozable(r)) { if (mSnoozeCriterionId != null) { mAssistants.notifyAssistantSnoozedLocked(r, mSnoozeCriterionId); @@ -7761,7 +7809,11 @@ public class NotificationManagerService extends SystemService { cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, mSendDelete, childrenFlagChecker, mReason, mCancellationElapsedTimeMs); - updateLightsLocked(); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.updateLightsLocked(); + } else { + updateLightsLocked(); + } if (mShortcutHelper != null) { mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */, @@ -8054,7 +8106,14 @@ public class NotificationManagerService extends SystemService { int buzzBeepBlinkLoggingCode = 0; if (!r.isHidden()) { - buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r, + new NotificationAttentionHelper.Signals( + mUserProfiles.isCurrentProfile(r.getUserId()), + mListenerHints)); + } else { + buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r); + } } if (notification.getSmallIcon() != null) { @@ -9034,7 +9093,13 @@ public class NotificationManagerService extends SystemService { || interruptiveChanged; if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { - buzzBeepBlinkLocked(record); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.buzzBeepBlinkLocked(record, + new NotificationAttentionHelper.Signals( + mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints)); + } else { + buzzBeepBlinkLocked(record); + } // Log alert after change in intercepted state to Zen Log as well ZenLog.traceAlertOnUpdatedIntercept(record); @@ -9408,18 +9473,22 @@ public class NotificationManagerService extends SystemService { }); } - // sound - if (canceledKey.equals(mSoundNotificationKey)) { - clearSoundLocked(); - } + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.clearEffectsLocked(canceledKey); + } else { + // sound + if (canceledKey.equals(mSoundNotificationKey)) { + clearSoundLocked(); + } - // vibrate - if (canceledKey.equals(mVibrateNotificationKey)) { - clearVibrateLocked(); - } + // vibrate + if (canceledKey.equals(mVibrateNotificationKey)) { + clearVibrateLocked(); + } - // light - mLights.remove(canceledKey); + // light + mLights.remove(canceledKey); + } } // Record usage stats @@ -9768,7 +9837,11 @@ public class NotificationManagerService extends SystemService { cancellationElapsedTimeMs); } } - updateLightsLocked(); + if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + mAttentionHelper.updateLightsLocked(); + } else { + updateLightsLocked(); + } } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java new file mode 100644 index 000000000000..81867df74abd --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -0,0 +1,1973 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://`www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.GROUP_ALERT_ALL; +import static android.app.Notification.GROUP_ALERT_CHILDREN; +import static android.app.Notification.GROUP_ALERT_SUMMARY; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.media.AudioAttributes.USAGE_NOTIFICATION; +import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +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.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.Notification.Builder; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Handler; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; +import androidx.test.runner.AndroidJUnit4; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; +import com.android.internal.config.sysui.TestableFlagResolver; +import com.android.internal.logging.InstanceIdSequence; +import com.android.internal.logging.InstanceIdSequenceFake; +import com.android.internal.util.IntPair; +import com.android.server.UiServiceTestCase; +import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; +import com.android.server.pm.PackageManagerService; +import java.util.Objects; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@SuppressLint("GuardedBy") +public class NotificationAttentionHelperTest extends UiServiceTestCase { + + @Mock AudioManager mAudioManager; + @Mock Vibrator mVibrator; + @Mock android.media.IRingtonePlayer mRingtonePlayer; + @Mock LogicalLight mLight; + @Mock + NotificationManagerService.WorkerHandler mHandler; + @Mock + NotificationUsageStats mUsageStats; + @Mock + IAccessibilityManager mAccessibilityService; + @Mock + KeyguardManager mKeyguardManager; + NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); + private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( + 1 << 30); + + private NotificationManagerService mService; + private String mPkg = "com.android.server.notification"; + private int mId = 1001; + private int mOtherId = 1002; + private String mTag = null; + private int mUid = 1000; + private int mPid = 2000; + private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser()); + private NotificationChannel mChannel; + + private NotificationAttentionHelper mAttentionHelper; + private TestableFlagResolver mTestFlagResolver = new TestableFlagResolver(); + private AccessibilityManager mAccessibilityManager; + private static final NotificationAttentionHelper.Signals DEFAULT_SIGNALS = + new NotificationAttentionHelper.Signals(false, 0); + + private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1); + private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0); + + private static final long[] CUSTOM_VIBRATION = new long[] { + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 }; + private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI; + private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); + private static final int CUSTOM_LIGHT_COLOR = Color.BLACK; + private static final int CUSTOM_LIGHT_ON = 10000; + private static final int CUSTOM_LIGHT_OFF = 10000; + private static final int MAX_VIBRATION_DELAY = 1000; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + getContext().addMockSystemService(Vibrator.class, mVibrator); + + when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); + when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); + when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); + when(mVibrator.hasFrequencyControl()).thenReturn(false); + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); + + long serviceReturnValue = IntPair.of( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, + AccessibilityEvent.TYPES_ALL_MASK); + when(mAccessibilityService.addClient(any(), anyInt())).thenReturn(serviceReturnValue); + mAccessibilityManager = + new AccessibilityManager(getContext(), Handler.getMain(), mAccessibilityService, 0, + true); + verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt()); + assertTrue(mAccessibilityManager.isEnabled()); + + // TODO (b/291907312): remove feature flag + mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true); + + mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, + mNotificationInstanceIdSequence)); + + initAttentionHelper(mTestFlagResolver); + + mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH); + } + + private void initAttentionHelper(TestableFlagResolver flagResolver) { + mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class), + mAccessibilityManager, getContext().getPackageManager(), mUsageStats, + mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver); + mAttentionHelper.setVibratorHelper(new VibratorHelper(getContext())); + mAttentionHelper.setAudioManager(mAudioManager); + mAttentionHelper.setSystemReady(true); + mAttentionHelper.setLights(mLight); + mAttentionHelper.setScreenOn(false); + mAttentionHelper.setAccessibilityManager(mAccessibilityManager); + mAttentionHelper.setKeyguardManager(mKeyguardManager); + mAttentionHelper.setScreenOn(false); + mAttentionHelper.setInCallStateOffHook(false); + mAttentionHelper.mNotificationPulseEnabled = true; + } + + // + // Convenience functions for creating notification records + // + + private NotificationRecord getNoisyOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + true /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBeepyNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBeepyOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBeepyOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getQuietNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getQuietOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + false /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getQuietOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getInsistentBeepyNotification() { + return getNotificationRecord(mId, true /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getInsistentBeepyOnceNotification() { + return getNotificationRecord(mId, true /* insistent */, true /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getInsistentBeepyLeanbackNotification() { + return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBuzzyNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + false /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBuzzyOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + false /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBuzzyOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getInsistentBuzzyNotification() { + return getNotificationRecord(mId, true /* insistent */, false /* once */, + false /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getBuzzyBeepyNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, true /* buzzy*/, false /* lights */); + } + + private NotificationRecord getLightsNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + false /* noisy */, false /* buzzy*/, true /* lights */); + } + + private NotificationRecord getLightsOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, false /* buzzy*/, true /* lights */); + } + + private NotificationRecord getCallRecord(int id, NotificationChannel channel, boolean looping) { + final Builder builder = new Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH); + Notification n = builder.build(); + if (looping) { + n.flags |= Notification.FLAG_INSISTENT; + } + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, + mPid, n, mUser, null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); + mService.addNotification(r); + + return r; + } + + private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy, + lights, null, Notification.GROUP_ALERT_ALL, false); + } + + private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, + boolean once, + boolean noisy, boolean buzzy, boolean lights) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, + true, + null, Notification.GROUP_ALERT_ALL, true); + } + + private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) { + return getNotificationRecord(mId, false, false, true, false, false, true, true, true, + groupKey, groupAlertBehavior, false); + } + + private NotificationRecord getLightsNotificationRecord(String groupKey, + int groupAlertBehavior) { + return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, + true, true, groupKey, groupAlertBehavior, false); + } + + private NotificationRecord getNotificationRecord(int id, + boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, + boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, + boolean isLeanback) { + + final Builder builder = new Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH) + .setOnlyAlertOnce(once); + + int defaults = 0; + if (noisy) { + if (defaultSound) { + defaults |= Notification.DEFAULT_SOUND; + mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, + Notification.AUDIO_ATTRIBUTES_DEFAULT); + } else { + builder.setSound(CUSTOM_SOUND); + mChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES); + } + } else { + mChannel.setSound(null, null); + } + if (buzzy) { + if (defaultVibration) { + defaults |= Notification.DEFAULT_VIBRATE; + } else { + builder.setVibrate(CUSTOM_VIBRATION); + mChannel.setVibrationPattern(CUSTOM_VIBRATION); + } + mChannel.enableVibration(true); + } else { + mChannel.setVibrationPattern(null); + mChannel.enableVibration(false); + } + + if (lights) { + if (defaultLights) { + defaults |= Notification.DEFAULT_LIGHTS; + } else { + builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF); + } + mChannel.enableLights(true); + } else { + mChannel.enableLights(false); + } + builder.setDefaults(defaults); + + builder.setGroup(groupKey); + builder.setGroupAlertBehavior(groupAlertBehavior); + + Notification n = builder.build(); + if (insistent) { + n.flags |= Notification.FLAG_INSISTENT; + } + + Context context = spy(getContext()); + PackageManager packageManager = spy(context.getPackageManager()); + when(context.getPackageManager()).thenReturn(packageManager); + when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + .thenReturn(isLeanback); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, + mPid, n, mUser, null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(context, sbn, mChannel); + mService.addNotification(r); + return r; + } + + // + // Convenience functions for interacting with mocks + // + + private void verifyNeverBeep() throws RemoteException { + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any()); + } + + private void verifyBeepUnlooped() throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any()); + } + + private void verifyBeepLooped() throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any()); + } + + private void verifyBeep(int times) throws RemoteException { + verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any()); + } + + private void verifyNeverStopAudio() throws RemoteException { + verify(mRingtonePlayer, never()).stopAsync(); + } + + private void verifyStopAudio() throws RemoteException { + verify(mRingtonePlayer, times(1)).stopAsync(); + } + + private void verifyNeverVibrate() { + verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), + any(VibrationAttributes.class)); + } + + private void verifyVibrate() { + verifyVibrate(/* times= */ 1); + } + + private void verifyVibrate(int times) { + verifyVibrate(mVibrateOnceMatcher, times(times)); + } + + private void verifyVibrateLooped() { + verifyVibrate(mVibrateLoopMatcher, times(1)); + } + + private void verifyDelayedVibrateLooped() { + verifyVibrate(mVibrateLoopMatcher, timeout(MAX_VIBRATION_DELAY).times(1)); + } + + private void verifyDelayedVibrate(VibrationEffect effect) { + verifyVibrate(argument -> Objects.equals(effect, argument), + timeout(MAX_VIBRATION_DELAY).times(1)); + } + + private void verifyDelayedNeverVibrate() { + verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(), + anyString(), any(VibrationAttributes.class)); + } + + private void verifyVibrate(ArgumentMatcher effectMatcher, + VerificationMode verification) { + ArgumentCaptor captor = + ArgumentCaptor.forClass(VibrationAttributes.class); + verify(mVibrator, verification).vibrate(eq(Process.SYSTEM_UID), + eq(PackageManagerService.PLATFORM_PACKAGE_NAME), argThat(effectMatcher), + anyString(), captor.capture()); + assertEquals(0, (captor.getValue().getFlags() + & VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); + } + + private void verifyStopVibrate() { + int alarmClassUsageFilter = + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; + verify(mVibrator, times(1)).cancel(eq(alarmClassUsageFilter)); + } + + private void verifyNeverStopVibrate() { + verify(mVibrator, never()).cancel(); + verify(mVibrator, never()).cancel(anyInt()); + } + + private void verifyNeverLights() { + verify(mLight, never()).setFlashing(anyInt(), anyInt(), anyInt(), anyInt()); + } + + private void verifyLights() { + verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt()); + } + + // + // Tests + // + + @Test + public void testLights() throws Exception { + NotificationRecord r = getLightsNotification(); + r.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyLights(); + assertTrue(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeep() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + verifyNeverVibrate(); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLockedPrivateA11yRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PRIVATE; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mAttentionHelper.setAccessibilityManager(accessibilityManager); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + ArgumentCaptor eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification().publicVersion, event.getParcelableData()); + } + + @Test + public void testLockedOverridePrivateA11yRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(Notification.VISIBILITY_PRIVATE); + r.getNotification().visibility = Notification.VISIBILITY_PUBLIC; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mAttentionHelper.setAccessibilityManager(accessibilityManager); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + ArgumentCaptor eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification().publicVersion, event.getParcelableData()); + } + + @Test + public void testLockedPublicA11yNoRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PUBLIC; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mAttentionHelper.setAccessibilityManager(accessibilityManager); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + ArgumentCaptor eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification(), event.getParcelableData()); + } + + @Test + public void testUnlockedPrivateA11yNoRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PRIVATE; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mAttentionHelper.setAccessibilityManager(accessibilityManager); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + ArgumentCaptor eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification(), event.getParcelableData()); + } + + @Test + public void testBeepInsistently() throws Exception { + NotificationRecord r = getInsistentBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyBeepLooped(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoLeanbackBeep() throws Exception { + NotificationRecord r = getInsistentBeepyLeanbackNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoBeepForAutomotiveIfEffectsDisabled() throws Exception { + mAttentionHelper.setIsAutomotive(true); + mAttentionHelper.setNotificationEffectsEnabledForAutomotive(false); + + NotificationRecord r = getBeepyNotification(); + r.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + } + + @Test + public void testNoBeepForImportanceDefaultInAutomotiveIfEffectsEnabled() throws Exception { + mAttentionHelper.setIsAutomotive(true); + mAttentionHelper.setNotificationEffectsEnabledForAutomotive(true); + + NotificationRecord r = getBeepyNotification(); + r.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + } + + @Test + public void testBeepForImportanceHighInAutomotiveIfEffectsEnabled() throws Exception { + mAttentionHelper.setIsAutomotive(true); + mAttentionHelper.setNotificationEffectsEnabledForAutomotive(true); + + NotificationRecord r = getBeepyNotification(); + r.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + assertTrue(r.isInterruptive()); + } + + @Test + public void testNoInterruptionForMin() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setSystemImportance(NotificationManager.IMPORTANCE_MIN); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + verifyNeverVibrate(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoInterruptionForIntercepted() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setIntercepted(true); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + verifyNeverVibrate(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepTwice() throws Exception { + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepUnlooped(); + verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt()); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testHonorAlertOnlyOnceForBeep() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getBeepyOnceNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should not beep + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyNeverBeep(); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testNoisyUpdateDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverStopAudio(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getBeepyOnceNotification(); + s.isUpdate = true; + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + + verifyNeverStopAudio(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + /** + * Tests the case where the user re-posts a {@link Notification} with looping sound where + * {@link Notification.Builder#setOnlyAlertOnce(true)} has been called. This should silence + * the sound associated with the notification. + * @throws Exception + */ + @Test + public void testNoisyOnceUpdateDoesCancelAudio() throws Exception { + NotificationRecord r = getInsistentBeepyNotification(); + NotificationRecord s = getInsistentBeepyOnceNotification(); + s.isUpdate = true; + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + + verifyStopAudio(); + } + + @Test + public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + NotificationRecord other = getNoisyOtherNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + mAttentionHelper.buzzBeepBlinkLocked(other, DEFAULT_SIGNALS); // this takes the audio stream + Mockito.reset(mRingtonePlayer); + + // should not stop noise, since we no longer own it + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); // this no longer owns the stream + verifyNeverStopAudio(); + assertTrue(other.isInterruptive()); + assertNotEquals(-1, other.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietInterloperDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord other = getQuietOtherNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // should not stop noise, since it does not own it + mAttentionHelper.buzzBeepBlinkLocked(other, DEFAULT_SIGNALS); + verifyNeverStopAudio(); + } + + @Test + public void testQuietUpdateCancelsAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + Mockito.reset(mRingtonePlayer); + + // quiet update should stop making noise + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyStopAudio(); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietOnceUpdateCancelsAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietOnceNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + Mockito.reset(mRingtonePlayer); + + // stop making noise - this is a weird corner case, but quiet should override once + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyStopAudio(); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testInCallNotification() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper = spy(mAttentionHelper); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + mAttentionHelper.setInCallStateOffHook(true); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(mAttentionHelper, times(1)).playInCallNotification(); + verifyNeverBeep(); // doesn't play normal beep + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoDemoteSoundToVibrateIfVibrateGiven() throws Exception { + NotificationRecord r = getBuzzyBeepyNotification(); + assertTrue(r.getSound() != null); + + // the phone is quiet + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyDelayedVibrate(r.getVibration()); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoDemoteSoundToVibrateIfNonNotificationStream() throws Exception { + NotificationRecord r = getBeepyNotification(); + assertTrue(r.getSound() != null); + assertNull(r.getVibration()); + + // the phone is quiet + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverVibrate(); + verifyBeepUnlooped(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testDemoteSoundToVibrate() throws Exception { + NotificationRecord r = getBeepyNotification(); + assertTrue(r.getSound() != null); + assertNull(r.getVibration()); + + // the phone is quiet + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyDelayedVibrate( + mAttentionHelper.getVibratorHelper().createFallbackVibration( + /* insistent= */ false)); + verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(), + anyObject()); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testDemoteInsistentSoundToVibrate() throws Exception { + NotificationRecord r = getInsistentBeepyNotification(); + assertTrue(r.getSound() != null); + assertNull(r.getVibration()); + + // the phone is quiet + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyDelayedVibrateLooped(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + verifyVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testInsistentVibrate() { + NotificationRecord r = getInsistentBuzzyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyVibrateLooped(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testVibrateTwice() { + NotificationRecord r = getBuzzyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mVibrator); + + // update should vibrate + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testPostSilently() throws Exception { + NotificationRecord r = getBuzzyNotification(); + r.setPostSilently(true); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummarySilenceChild() throws Exception { + NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY); + + mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(child.isInterruptive()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummaryNoSilenceSummary() throws Exception { + NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + // summaries are never interruptive for notification counts + assertFalse(summary.isInterruptive()); + assertNotEquals(-1, summary.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummaryNoSilenceNonGroupChild() throws Exception { + NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_SUMMARY); + + mAttentionHelper.buzzBeepBlinkLocked(nonGroup, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + assertTrue(nonGroup.isInterruptive()); + assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildSilenceSummary() throws Exception { + NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + + verifyNeverBeep(); + assertFalse(summary.isInterruptive()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildNoSilenceChild() throws Exception { + NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN); + + mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + assertTrue(child.isInterruptive()); + assertNotEquals(-1, child.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildNoSilenceNonGroupSummary() throws Exception { + NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_CHILDREN); + + mAttentionHelper.buzzBeepBlinkLocked(nonGroup, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + assertTrue(nonGroup.isInterruptive()); + assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertAllNoSilenceGroup() throws Exception { + NotificationRecord group = getBeepyNotificationRecord("a", GROUP_ALERT_ALL); + + mAttentionHelper.buzzBeepBlinkLocked(group, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + assertTrue(group.isInterruptive()); + assertNotEquals(-1, group.getLastAudiblyAlertedMs()); + } + + @Test + public void testHonorAlertOnlyOnceForBuzz() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getBuzzyOnceNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mVibrator); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + + // update should not beep + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyNeverVibrate(); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoisyUpdateDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getBuzzyOnceNotification(); + s.isUpdate = true; + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + + verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + NotificationRecord other = getNoisyOtherNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + // this takes the vibrate stream + mAttentionHelper.buzzBeepBlinkLocked(other, DEFAULT_SIGNALS); + Mockito.reset(mVibrator); + + // should not stop vibrate, since we no longer own it + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); // this no longer owns the stream + verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertTrue(other.isInterruptive()); + assertNotEquals(-1, other.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietInterloperDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord other = getQuietOtherNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mVibrator); + + // should not stop noise, since it does not own it + mAttentionHelper.buzzBeepBlinkLocked(other, DEFAULT_SIGNALS); + verifyNeverStopVibrate(); + assertFalse(other.isInterruptive()); + assertEquals(-1, other.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietUpdateCancelsVibrate() { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyVibrate(); + + // quiet update should stop making noise + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietOnceUpdateCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietOnceNotification(); + s.isUpdate = true; + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyVibrate(); + + // stop making noise - this is a weird corner case, but quiet should override once + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testQuietUpdateCancelsDemotedVibrate() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + + // the phone is quiet + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyDelayedVibrate(mAttentionHelper.getVibratorHelper().createFallbackVibration(false)); + + // quiet update should stop making noise + mAttentionHelper.buzzBeepBlinkLocked(s, DEFAULT_SIGNALS); + verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + assertFalse(s.isInterruptive()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); + } + + @Test + public void testEmptyUriSoundTreatedAsNoSound() throws Exception { + NotificationChannel channel = new NotificationChannel("test", "test", IMPORTANCE_HIGH); + channel.setSound(Uri.EMPTY, null); + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, mUser, null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); + mService.addNotification(r); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testRepeatedSoundOverLimitMuted() throws Exception { + when(mUsageStats.isAlertRateLimited(any())).thenReturn(true); + + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testPostingSilentNotificationDoesNotAffectRateLimiting() throws Exception { + NotificationRecord r = getQuietNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(mUsageStats, never()).isAlertRateLimited(any()); + } + + @Test + public void testPostingGroupSuppressedDoesNotAffectRateLimiting() throws Exception { + NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + verify(mUsageStats, never()).isAlertRateLimited(any()); + } + + @Test + public void testGroupSuppressionFailureDoesNotAffectRateLimiting() { + NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + verify(mUsageStats, times(1)).isAlertRateLimited(any()); + } + + @Test + public void testCrossUserSoundMuted() throws Exception { + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + + int userId = mUser.getIdentifier() + 1; + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, UserHandle.of(userId), null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, + new NotificationChannel("test", "test", IMPORTANCE_HIGH)); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverBeep(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testA11yMinInitialPost() throws Exception { + NotificationRecord r = getQuietNotification(); + r.setSystemImportance(IMPORTANCE_MIN); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testA11yQuietInitialPost() throws Exception { + NotificationRecord r = getQuietNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testA11yQuietUpdate() throws Exception { + NotificationRecord r = getQuietNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testA11yCrossUserEventNotSent() throws Exception { + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + int userId = mUser.getIdentifier() + 1; + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, UserHandle.of(userId), null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, + new NotificationChannel("test", "test", IMPORTANCE_HIGH)); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testLightsScreenOn() { + mAttentionHelper.setScreenOn(true); + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertTrue(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsInCall() { + mAttentionHelper.setInCallStateOffHook(true); + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsSilentUpdate() { + NotificationRecord r = getLightsOnceNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyLights(); + assertTrue(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + + r = getLightsOnceNotification(); + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + // checks that lights happened once, i.e. this new call didn't trigger them again + verifyLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsUnimportant() { + NotificationRecord r = getLightsNotification(); + r.setSystemImportance(IMPORTANCE_LOW); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsNoLights() { + NotificationRecord r = getQuietNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsNoLightOnDevice() { + mAttentionHelper.mHasLight = false; + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsLightsOffGlobally() { + mAttentionHelper.mNotificationPulseEnabled = false; + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsDndIntercepted() { + NotificationRecord r = getLightsNotification(); + r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummaryNoLightsChild() { + NotificationRecord child = getLightsNotificationRecord("a", GROUP_ALERT_SUMMARY); + + mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS); + + verifyNeverLights(); + assertFalse(child.isInterruptive()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummaryLightsSummary() { + NotificationRecord summary = getLightsNotificationRecord("a", GROUP_ALERT_SUMMARY); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + + verifyLights(); + // summaries should never count for interruptiveness counts + assertFalse(summary.isInterruptive()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertSummaryLightsNonGroupChild() { + NotificationRecord nonGroup = getLightsNotificationRecord(null, GROUP_ALERT_SUMMARY); + + mAttentionHelper.buzzBeepBlinkLocked(nonGroup, DEFAULT_SIGNALS); + + verifyLights(); + assertTrue(nonGroup.isInterruptive()); + assertEquals(-1, nonGroup.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildNoLightsSummary() { + NotificationRecord summary = getLightsNotificationRecord("a", GROUP_ALERT_CHILDREN); + summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; + + mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS); + + verifyNeverLights(); + assertFalse(summary.isInterruptive()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildLightsChild() { + NotificationRecord child = getLightsNotificationRecord("a", GROUP_ALERT_CHILDREN); + + mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS); + + verifyLights(); + assertTrue(child.isInterruptive()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertChildLightsNonGroupSummary() { + NotificationRecord nonGroup = getLightsNotificationRecord(null, GROUP_ALERT_CHILDREN); + + mAttentionHelper.buzzBeepBlinkLocked(nonGroup, DEFAULT_SIGNALS); + + verifyLights(); + assertTrue(nonGroup.isInterruptive()); + assertEquals(-1, nonGroup.getLastAudiblyAlertedMs()); + } + + @Test + public void testGroupAlertAllLightsGroup() { + NotificationRecord group = getLightsNotificationRecord("a", GROUP_ALERT_ALL); + + mAttentionHelper.buzzBeepBlinkLocked(group, DEFAULT_SIGNALS); + + verifyLights(); + assertTrue(group.isInterruptive()); + assertEquals(-1, group.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsCheckCurrentUser() { + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + int userId = mUser.getIdentifier() + 10; + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, UserHandle.of(userId), null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, + new NotificationChannel("test", "test", IMPORTANCE_HIGH)); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testListenerHintCall() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord r = getCallRecord(1, ringtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)); + + verifyNeverBeep(); + } + + @Test + public void testListenerHintCall_notificationSound() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)); + + verifyBeepUnlooped(); + } + + @Test + public void testListenerHintNotification() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS)); + + verifyNeverBeep(); + } + + @Test + public void testListenerHintBoth() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord r = getCallRecord(1, ringtoneChannel, true); + NotificationRecord s = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS + | NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)); + mAttentionHelper.buzzBeepBlinkLocked(s, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS + | NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)); + + verifyNeverBeep(); + } + + @Test + public void testListenerHintNotification_callSound() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord r = getCallRecord(1, ringtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals(false, + NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS)); + + verifyBeepLooped(); + } + + @Test + public void testCannotInterruptRingtoneInsistentBeep() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + + NotificationRecord interrupter = getBeepyOtherNotification(); + assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyBeep(1); + + assertFalse(interrupter.isInterruptive()); + assertEquals(-1, interrupter.getLastAudiblyAlertedMs()); + } + + @Test + public void testRingtoneInsistentBeep_canUpdate() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification, + DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + Mockito.reset(mVibrator); + Mockito.reset(mRingtonePlayer); + + assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification, + DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + + // beep wasn't reset + verifyNeverBeep(); + verifyNeverVibrate(); + verifyNeverStopAudio(); + verifyNeverStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification, + DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + + mAttentionHelper.clearSoundLocked(); + mAttentionHelper.clearVibrateLocked(); + + verifyStopAudio(); + verifyStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay() + throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification, + DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + verifyNeverVibrate(); + + mAttentionHelper.clearSoundLocked(); + mAttentionHelper.clearVibrateLocked(); + + verifyStopAudio(); + verifyDelayedNeverVibrate(); + } + + @Test + public void testCannotInterruptRingtoneInsistentBuzz() { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.EMPTY, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification, + DEFAULT_SIGNALS)); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyVibrateLooped(); + + NotificationRecord interrupter = getBuzzyOtherNotification(); + assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS)); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyVibrate(1); + + assertFalse(interrupter.isInterruptive()); + assertEquals(-1, interrupter.getLastAudiblyAlertedMs()); + } + + @Test + public void testCanInterruptRingtoneNonInsistentBeep() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepUnlooped(); + + NotificationRecord interrupter = getBeepyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyBeep(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testCanInterruptRingtoneNonInsistentBuzz() { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(null, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyVibrate(); + + NotificationRecord interrupter = getBuzzyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyVibrate(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + + mAttentionHelper.clearSoundLocked(); + + NotificationRecord interrupter = getBeepyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyBeep(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testRingtoneInsistentBuzz_doesNotBlockFutureSoundsOnceStopped() { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(null, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyVibrateLooped(); + + mAttentionHelper.clearVibrateLocked(); + + NotificationRecord interrupter = getBuzzyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyVibrate(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testCanInterruptNonRingtoneInsistentBeep() throws Exception { + NotificationChannel fakeRingtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + verifyBeepLooped(); + + NotificationRecord interrupter = getBeepyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyBeep(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testCanInterruptNonRingtoneInsistentBuzz() { + NotificationChannel fakeRingtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + fakeRingtoneChannel.enableVibration(true); + fakeRingtoneChannel.setSound(null, + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION).build()); + NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true); + + mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS); + + NotificationRecord interrupter = getBuzzyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + + verifyVibrate(2); + + assertTrue(interrupter.isInterruptive()); + } + + @Test + public void testBubbleSuppressedNotificationDoesntMakeSound() { + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( + mock(PendingIntent.class), mock(Icon.class)) + .build(); + + NotificationRecord record = getBuzzyNotification(); + metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); + record.getNotification().setBubbleMetadata(metadata); + record.setAllowBubble(true); + record.getNotification().flags |= FLAG_BUBBLE; + record.isUpdate = true; + record.setInterruptive(false); + + mAttentionHelper.buzzBeepBlinkLocked(record, DEFAULT_SIGNALS); + verifyNeverVibrate(); + } + + @Test + public void testOverflowBubbleSuppressedNotificationDoesntMakeSound() { + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( + mock(PendingIntent.class), mock(Icon.class)) + .build(); + + NotificationRecord record = getBuzzyNotification(); + metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); + record.getNotification().setBubbleMetadata(metadata); + record.setFlagBubbleRemoved(true); + record.setAllowBubble(true); + record.isUpdate = true; + record.setInterruptive(false); + + mAttentionHelper.buzzBeepBlinkLocked(record, DEFAULT_SIGNALS); + verifyNeverVibrate(); + } + + @Test + public void testBubbleUpdateMakesSound() { + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( + mock(PendingIntent.class), mock(Icon.class)) + .build(); + + NotificationRecord record = getBuzzyNotification(); + record.getNotification().setBubbleMetadata(metadata); + record.setAllowBubble(true); + record.getNotification().flags |= FLAG_BUBBLE; + record.isUpdate = true; + record.setInterruptive(true); + + mAttentionHelper.buzzBeepBlinkLocked(record, DEFAULT_SIGNALS); + verifyVibrate(1); + } + + @Test + public void testNewBubbleSuppressedNotifMakesSound() { + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( + mock(PendingIntent.class), mock(Icon.class)) + .build(); + + NotificationRecord record = getBuzzyNotification(); + metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); + record.getNotification().setBubbleMetadata(metadata); + record.setAllowBubble(true); + record.getNotification().flags |= FLAG_BUBBLE; + record.isUpdate = false; + record.setInterruptive(true); + + mAttentionHelper.buzzBeepBlinkLocked(record, DEFAULT_SIGNALS); + verifyVibrate(1); + } + + @Test + public void testStartFlashNotificationEvent_receiveBeepyNotification() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + verifyNeverVibrate(); + verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(), + eq(r.getSbn().getPackageName())); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testStartFlashNotificationEvent_receiveBuzzyNotification() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + verifyVibrate(); + verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(), + eq(r.getSbn().getPackageName())); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testStartFlashNotificationEvent_receiveBuzzyBeepyNotification() throws Exception { + NotificationRecord r = getBuzzyBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyBeepUnlooped(); + verifyDelayedVibrate(r.getVibration()); + verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(), + eq(r.getSbn().getPackageName())); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testStartFlashNotificationEvent_receiveBuzzyBeepyNotification_ringerModeSilent() + throws Exception { + NotificationRecord r = getBuzzyBeepyNotification(); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verifyNeverBeep(); + verifyNeverVibrate(); + verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(), + eq(r.getSbn().getPackageName())); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + static class VibrateRepeatMatcher implements ArgumentMatcher { + private final int mRepeatIndex; + + VibrateRepeatMatcher(int repeatIndex) { + mRepeatIndex = repeatIndex; + } + + @Override + public boolean matches(VibrationEffect actual) { + if (actual instanceof VibrationEffect.Composed + && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) { + return true; + } + // All non-waveform effects are essentially one shots. + return mRepeatIndex == -1; + } + + @Override + public String toString() { + return "repeatIndex=" + mRepeatIndex; + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 4576e9be07ad..9543a2de1e13 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -84,6 +84,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; +import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; @@ -272,6 +273,7 @@ import com.google.common.collect.ImmutableList; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -279,6 +281,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -372,6 +375,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private DevicePolicyManagerInternal mDevicePolicyManager; @Mock private PowerManager mPowerManager; + @Mock + private LightsManager mLightsManager; private final ArrayList mAcquiredWakeLocks = new ArrayList<>(); private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory = new TestPostNotificationTrackerFactory(); @@ -503,9 +508,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setDpmAppOppsExemptFromDismissal(false); - mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger, - mNotificationInstanceIdSequence); - // Use this testable looper. mTestableLooper = TestableLooper.get(this); // MockPackageManager - default returns ApplicationInfo with matching calling UID @@ -527,8 +529,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Object[] args = invocation.getArguments(); return (int) args[1] == mUid; }); - final LightsManager mockLightsManager = mock(LightsManager.class); - when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); + when(mLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); @@ -601,6 +602,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return wl; }); + // TODO (b/291907312): remove feature flag + mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false); + initNMS(); + } + + private void initNMS() throws Exception { + mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger, + mNotificationInstanceIdSequence); + // apps allowed as convos mService.setStringArrayResourceValue(PKG_O); @@ -617,7 +627,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, - mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, + mLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm, mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager, @@ -628,11 +638,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); + Mockito.reset(mHistoryManager); verify(mHistoryManager, never()).onBootPhaseAppsCanStart(); mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); verify(mHistoryManager).onBootPhaseAppsCanStart(); - mService.setAudioManager(mAudioManager); + // TODO b/291907312: remove feature flag + if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) { + mService.mAttentionHelper.setAudioManager(mAudioManager); + } else { + mService.setAudioManager(mAudioManager); + } mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext); mService.setStrongAuthTracker(mStrongAuthTracker); @@ -1652,6 +1668,23 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(call.postDurationMillisLogged).isGreaterThan(0); } + @Test + public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception { + // TODO b/291907312: remove feature flag + mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true); + // Cleanup NMS before re-initializing + if (mService != null) { + try { + mService.onDestroy(); + } catch (IllegalStateException | IllegalArgumentException e) { + // can throw if a broadcast receiver was never registered + } + } + initNMS(); + + testEnqueueNotificationWithTag_WritesExpectedLogs(); + } + @Test public void testEnqueueNotificationWithTag_LogsOnMajorUpdates() throws Exception { final String tag = "testEnqueueNotificationWithTag_LogsOnMajorUpdates"; @@ -9014,6 +9047,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mockPlayer).stopAsync(); } + @Test + public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor() + throws Exception { + // TODO b/291907312: remove feature flag + mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true); + // Cleanup NMS before re-initializing + if (mService != null) { + try { + mService.onDestroy(); + } catch (IllegalStateException | IllegalArgumentException e) { + // can throw if a broadcast receiver was never registered + } + } + initNMS(); + + testOnBubbleMetadataChangedToSuppressNotification_soundStopped(); + } + @Test public void testGrantInlineReplyUriPermission_recordExists() throws Exception { int userId = UserManager.isHeadlessSystemUserMode() -- GitLab From 8ac2e966ff3fa7af71a190b12fc1d05c82b11aad Mon Sep 17 00:00:00 2001 From: mrulhania Date: Mon, 21 Aug 2023 17:28:23 -0700 Subject: [PATCH 59/95] device aware self revoke and one time session timeout Bug: 283978092 Test: atest RevokeSelfPermissionTest Test: atest OneTimePermissionTest Test: atest DevicePermissionsTest Change-Id: I0adef572133a288c17e43b3e1b8adcf53ff68114 --- core/api/system-current.txt | 6 +- .../permission/IPermissionController.aidl | 4 +- .../permission/IPermissionManager.aidl | 2 +- .../PermissionControllerManager.java | 15 +++-- .../PermissionControllerService.java | 60 +++++++++++++++++-- .../android/permission/PermissionManager.java | 10 +++- packages/Shell/AndroidManifest.xml | 1 + .../OneTimePermissionUserManager.java | 39 +++++++----- .../permission/PermissionManagerService.java | 6 +- .../permission/DevicePermissionPolicy.kt | 23 ++++++- 10 files changed, 127 insertions(+), 39 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7c78f09f0057..69ed5aeda7de 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10635,12 +10635,14 @@ package android.permission { method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); - method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); + method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); + method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer>>); - method @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List, @NonNull Runnable); + method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List, @NonNull Runnable); + method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List, int, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index e3f02e73a41f..1cb7930928d0 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -43,7 +43,7 @@ oneway interface IPermissionController { void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName, in AdminPermissionControlParams params, in AndroidFuture callback); void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback); - void notifyOneTimePermissionSessionTimeout(String packageName); + void notifyOneTimePermissionSessionTimeout(String packageName, int deviceId); void updateUserSensitiveForApp(int uid, in AndroidFuture callback); void getPrivilegesDescriptionStringForProfile( in String deviceProfileName, @@ -60,5 +60,5 @@ oneway interface IPermissionController { in String packageName, in AndroidFuture callback); void revokeSelfPermissionsOnKill(in String packageName, in List permissions, - in AndroidFuture callback); + int deviceId, in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 18ede44dca20..7cecfdca851a 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -78,7 +78,7 @@ interface IPermissionManager { List getSplitPermissions(); @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS") - void startOneTimePermissionSession(String packageName, int userId, long timeout, + void startOneTimePermissionSession(String packageName, int deviceId, int userId, long timeout, long revokeAfterKilledDelay); @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS") diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 84a197a96531..ba7591814a7b 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -764,13 +764,14 @@ public final class PermissionControllerManager { * inactive. * * @param packageName The package which became inactive - * + * @param deviceId The device ID refers either the primary device i.e. the phone or + * a virtual device. See {@link Context#DEVICE_ID_DEFAULT} * @hide */ @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) - public void notifyOneTimePermissionSessionTimeout(@NonNull String packageName) { - mRemoteService.run( - service -> service.notifyOneTimePermissionSessionTimeout(packageName)); + public void notifyOneTimePermissionSessionTimeout(@NonNull String packageName, int deviceId) { + mRemoteService.run(service -> service.notifyOneTimePermissionSessionTimeout( + packageName, deviceId)); } /** @@ -930,12 +931,14 @@ public final class PermissionControllerManager { @NonNull List permissions) { mRemoteService.postAsync(service -> { AndroidFuture callback = new AndroidFuture<>(); - service.revokeSelfPermissionsOnKill(packageName, permissions, callback); + service.revokeSelfPermissionsOnKill(packageName, permissions, mContext.getDeviceId(), + callback); return callback; }).whenComplete((result, err) -> { if (err != null) { Log.e(TAG, "Failed to self revoke " + String.join(",", permissions) - + " for package " + packageName, err); + + " for package " + packageName + ", and device " + mContext.getDeviceId(), + err); } }); } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 11005a6d265b..9fe2599dd39b 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -30,6 +30,7 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.Manifest; import android.annotation.BinderThread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -37,6 +38,7 @@ import android.app.Service; import android.app.admin.DevicePolicyManager.PermissionGrantState; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -46,6 +48,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.permission.PermissionControllerManager.CountPermissionAppsFlag; +import android.permission.flags.Flags; import android.util.ArrayMap; import android.util.Log; @@ -296,12 +299,31 @@ public abstract class PermissionControllerService extends Service { * This method is called at the end of a one-time permission session * * @param packageName The package that has been inactive + * + * @deprecated Implement {@link #onOneTimePermissionSessionTimeout(String, int)} instead. */ + @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String packageName) { throw new AbstractMethodError("Must be overridden in implementing class"); } + /** + * Called when a package is considered inactive based on the criteria given by + * {@link PermissionManager#startOneTimePermissionSession(String, long, long, int, int)}. + * This method is called at the end of a one-time permission session + * + * @param packageName The package that has been inactive + * @param deviceId The device ID refers either the primary device i.e. the phone or + * a virtual device. See {@link Context#DEVICE_ID_DEFAULT} + */ + @BinderThread + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + public void onOneTimePermissionSessionTimeout(@NonNull String packageName, + int deviceId) { + onOneTimePermissionSessionTimeout(packageName); + } + /** * Get the platform permissions which belong to a particular permission group * @@ -341,13 +363,42 @@ public abstract class PermissionControllerService extends Service { * @param callback Callback waiting for operation to be complete. * * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection) + * + * @deprecated Implement {@link #onRevokeSelfPermissionsOnKill(String, List, int, Runnable)} + * instead. */ + @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, @NonNull List permissions, @NonNull Runnable callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } + /** + * Triggers the revocation of one or more permissions for a package and device. + * This should only be called at the request of {@code packageName}. + *

+ * Background permissions which have no corresponding foreground permission still granted once + * the revocation is effective will also be revoked. + *

+ * This revocation happens asynchronously and kills all processes running in the same UID as + * {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + * @param deviceId The device ID refers either the primary device i.e. the phone or + * a virtual device. See {@link Context#DEVICE_ID_DEFAULT} + * @param callback Callback waiting for operation to be complete. + * + * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection) + */ + @BinderThread + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, + @NonNull List permissions, int deviceId, @NonNull Runnable callback) { + onRevokeSelfPermissionsOnKill(packageName, permissions, callback); + } + // TODO(b/272129940): Remove this API and device profile role description when we drop T // support. /** @@ -613,12 +664,12 @@ public abstract class PermissionControllerService extends Service { } @Override - public void notifyOneTimePermissionSessionTimeout(String packageName) { + public void notifyOneTimePermissionSessionTimeout(String packageName, int deviceId) { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); packageName = Preconditions.checkNotNull(packageName, "packageName cannot be null"); - onOneTimePermissionSessionTimeout(packageName); + onOneTimePermissionSessionTimeout(packageName, deviceId); } @Override @@ -710,7 +761,8 @@ public abstract class PermissionControllerService extends Service { @Override public void revokeSelfPermissionsOnKill(@NonNull String packageName, - @NonNull List permissions, @NonNull AndroidFuture callback) { + @NonNull List permissions, int deviceId, + @NonNull AndroidFuture callback) { try { Objects.requireNonNull(callback); @@ -721,7 +773,7 @@ public abstract class PermissionControllerService extends Service { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); } - onRevokeSelfPermissionsOnKill(packageName, permissions, + onRevokeSelfPermissionsOnKill(packageName, permissions, deviceId, () -> callback.complete(null)); } catch (Throwable t) { callback.completeExceptionally(t); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7d3921049712..e10ea10e2a29 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -213,6 +213,12 @@ public final class PermissionManager { */ public static final boolean DEBUG_TRACE_PERMISSION_UPDATES = false; + /** + * Additional debug log for virtual device permissions. + * @hide + */ + public static final boolean DEBUG_DEVICE_PERMISSIONS = false; + /** * Intent extra: List of PermissionGroupUsages *

@@ -1392,8 +1398,8 @@ public final class PermissionManager { @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer, @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { - mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, revokeAfterKilledDelayMillis); + mPermissionManager.startOneTimePermissionSession(packageName, mContext.getDeviceId(), + mContext.getUserId(), timeoutMillis, revokeAfterKilledDelayMillis); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 15620b7023e2..11ae9c35898b 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -206,6 +206,7 @@ + - - + + + diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index 7e0c2071dc86..fa56516e0d22 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -31,35 +31,35 @@ android:orientation="horizontal" android:clickable="true" android:focusable="true" - android:paddingStart="8dp"> + android:paddingStart="16dp"> Date: Tue, 19 Sep 2023 14:28:24 +0000 Subject: [PATCH 62/95] Add possibility to ignore animationLimits in RampAnimator Bug: b/283447291 Test: atest DisplayPowerControllerTest atest DisplayPowerController2Test Change-Id: I586de60cc653397c63796b035764e584af503256 --- .../display/DisplayPowerController.java | 2 +- .../display/DisplayPowerController2.java | 10 ++- .../android/server/display/RampAnimator.java | 54 +++++++++--- .../display/DisplayPowerController2Test.java | 82 ++++++++++--------- .../display/DisplayPowerControllerTest.java | 80 +++++++++--------- .../server/display/RampAnimatorTest.java | 36 +++++++- 6 files changed, 167 insertions(+), 97 deletions(-) diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 320684fe3a0f..f86b8ca5208b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2425,7 +2425,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget + ", rate=" + rate); } - if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) { + if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate, false)) { Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target); String propertyKey = "debug.tracing.screen_brightness"; diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 597738e8b293..da299813d525 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -1547,7 +1547,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal SCREEN_ANIMATION_RATE_MINIMUM); } else if (customTransitionRate > 0) { animateScreenBrightness(animateValue, sdrAnimateValue, - customTransitionRate); + customTransitionRate, /* ignoreAnimationLimits = */true); } else { boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; @@ -2017,11 +2017,17 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } private void animateScreenBrightness(float target, float sdrTarget, float rate) { + animateScreenBrightness(target, sdrTarget, rate, /* ignoreAnimationLimits = */false); + } + + private void animateScreenBrightness(float target, float sdrTarget, float rate, + boolean ignoreAnimationLimits) { if (DEBUG) { Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget + ", rate=" + rate); } - if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) { + if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate, + ignoreAnimationLimits)) { Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target); String propertyKey = "debug.tracing.screen_brightness"; diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 378cdba09520..5ba042c51a45 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -30,6 +30,8 @@ class RampAnimator { private final T mObject; private final FloatProperty mProperty; + private final Clock mClock; + private float mCurrentValue; // target in HLG space @@ -47,10 +49,14 @@ class RampAnimator { private boolean mFirstTime = true; - RampAnimator(T object, FloatProperty property) { + this(object, property, System::nanoTime); + } + + RampAnimator(T object, FloatProperty property, Clock clock) { mObject = object; mProperty = property; + mClock = clock; } /** @@ -66,15 +72,24 @@ class RampAnimator { /** * Sets the animation target and the rate of this ramp animator. - * + * Animation rate will be set ignoring maxTime animation limits * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * * @param targetLinear The target value. * @param rate The convergence rate in units per second, or 0 to set the value immediately. + * @param ignoreAnimationLimits if mAnimationIncreaseMaxTimeSecs and + * mAnimationDecreaseMaxTimeSecs should be respected when adjusting + * animation speed * @return True if the target differs from the previous target. */ - boolean setAnimationTarget(float targetLinear, float rate) { + boolean setAnimationTarget(float targetLinear, float rate, boolean ignoreAnimationLimits) { + float maxIncreaseTimeSecs = ignoreAnimationLimits ? 0 : mAnimationIncreaseMaxTimeSecs; + float maxDecreaseTimeSecs = ignoreAnimationLimits ? 0 : mAnimationDecreaseMaxTimeSecs; + return setAnimationTarget(targetLinear, rate, maxIncreaseTimeSecs, maxDecreaseTimeSecs); + } + private boolean setAnimationTarget(float targetLinear, float rate, + float maxIncreaseTimeSecs, float maxDecreaseTimeSecs) { // Convert the target from the linear into the HLG space. final float target = BrightnessUtils.convertLinearToGamma(targetLinear); @@ -94,12 +109,12 @@ class RampAnimator { } // Adjust the rate so that we do not exceed our maximum animation time. - if (target > mCurrentValue && mAnimationIncreaseMaxTimeSecs > 0.0f - && ((target - mCurrentValue) / rate) > mAnimationIncreaseMaxTimeSecs) { - rate = (target - mCurrentValue) / mAnimationIncreaseMaxTimeSecs; - } else if (target < mCurrentValue && mAnimationDecreaseMaxTimeSecs > 0.0f - && ((mCurrentValue - target) / rate) > mAnimationDecreaseMaxTimeSecs) { - rate = (mCurrentValue - target) / mAnimationDecreaseMaxTimeSecs; + if (target > mCurrentValue && maxIncreaseTimeSecs > 0.0f + && ((target - mCurrentValue) / rate) > maxIncreaseTimeSecs) { + rate = (target - mCurrentValue) / maxIncreaseTimeSecs; + } else if (target < mCurrentValue && maxDecreaseTimeSecs > 0.0f + && ((mCurrentValue - target) / rate) > maxDecreaseTimeSecs) { + rate = (mCurrentValue - target) / maxDecreaseTimeSecs; } // Adjust the rate based on the closest target. @@ -124,7 +139,7 @@ class RampAnimator { if (!mAnimating && target != mCurrentValue) { mAnimating = true; mAnimatedValue = mCurrentValue; - mLastFrameTimeNanos = System.nanoTime(); + mLastFrameTimeNanos = mClock.nanoTime(); } return changed; @@ -184,6 +199,13 @@ class RampAnimator { void onAnimationEnd(); } + interface Clock { + /** + * Returns current system time in nanoseconds. + */ + long nanoTime(); + } + static class DualRampAnimator { private final Choreographer mChoreographer; private final RampAnimator mFirst; @@ -219,11 +241,17 @@ class RampAnimator { * @param linearFirstTarget The first target value in linear space. * @param linearSecondTarget The second target value in linear space. * @param rate The convergence rate in units per second, or 0 to set the value immediately. + * @param ignoreAnimationLimits if mAnimationIncreaseMaxTimeSecs and + * mAnimationDecreaseMaxTimeSecs should be respected + * when adjusting animation speed * @return True if either target differs from the previous target. */ - public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) { - boolean animationTargetChanged = mFirst.setAnimationTarget(linearFirstTarget, rate); - animationTargetChanged |= mSecond.setAnimationTarget(linearSecondTarget, rate); + public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate, + boolean ignoreAnimationLimits) { + boolean animationTargetChanged = mFirst.setAnimationTarget(linearFirstTarget, rate, + ignoreAnimationLimits); + animationTargetChanged |= mSecond.setAnimationTarget(linearSecondTarget, rate, + ignoreAnimationLimits); boolean shouldBeAnimating = isAnimating(); if (shouldBeAnimating != mAwaitingCallback) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 89e28cb8ab83..d6b2c8732e50 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -335,9 +335,9 @@ public final class DisplayPowerController2Test { when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness); listener.onBrightnessChanged(leadBrightness); advanceTime(1); // Send messages, run updatePowerState - verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat()); + verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat(), eq(false)); verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(), - anyFloat()); + anyFloat(), eq(false)); clearInvocations(mHolder.animator, followerDpc.animator); @@ -351,9 +351,9 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -383,9 +383,9 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -413,9 +413,9 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -445,9 +445,9 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -484,11 +484,11 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux); verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness); when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness); @@ -515,10 +515,10 @@ public final class DisplayPowerController2Test { // The second time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false)); verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux); verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false)); } @Test @@ -552,7 +552,7 @@ public final class DisplayPowerController2Test { followerListener.onBrightnessChanged(initialFollowerBrightness); advanceTime(1); verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(followerDpc.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -573,11 +573,11 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness); @@ -588,7 +588,7 @@ public final class DisplayPowerController2Test { mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc); advanceTime(1); verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); when(followerDpc.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -606,10 +606,11 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); - verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat()); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat(), + anyBoolean()); verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -652,9 +653,9 @@ public final class DisplayPowerController2Test { secondFollowerListener.onBrightnessChanged(initialFollowerBrightness); advanceTime(1); verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(followerHolder.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -677,11 +678,11 @@ public final class DisplayPowerController2Test { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); @@ -692,9 +693,9 @@ public final class DisplayPowerController2Test { mHolder.dpc.stop(); advanceTime(1); verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); clearInvocations(followerHolder.animator, secondFollowerHolder.animator); } @@ -716,7 +717,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness); @@ -730,7 +731,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_MINIMUM)); + eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false)); } @Test @@ -756,7 +757,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness); @@ -769,7 +770,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_MINIMUM)); + eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false)); } @Test @@ -823,7 +824,7 @@ public final class DisplayPowerController2Test { verify(mHolder.screenOffBrightnessSensorController, atLeastOnce()) .getAutomaticScreenBrightness(); - verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false)); } @Test @@ -861,7 +862,7 @@ public final class DisplayPowerController2Test { verify(mHolder.screenOffBrightnessSensorController, atLeastOnce()) .getAutomaticScreenBrightness(); - verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false)); } @Test @@ -1104,7 +1105,8 @@ public final class DisplayPowerController2Test { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged - verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); + verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(), + eq(false)); } @Test @@ -1213,7 +1215,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); brightness = 0.05f; @@ -1225,7 +1227,7 @@ public final class DisplayPowerController2Test { // The second time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE), eq(false)); brightness = 0.9f; when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( @@ -1235,7 +1237,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState // The third time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE), eq(false)); } @Test @@ -1272,7 +1274,7 @@ public final class DisplayPowerController2Test { advanceTime(1); // Run updatePowerState verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(), - eq(transitionRate)); + eq(transitionRate), eq(true)); } /** diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 971ece365f30..c53e763068b7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -336,9 +336,9 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(leadBrightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness); @@ -354,9 +354,9 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -387,9 +387,9 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -418,9 +418,9 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -451,9 +451,9 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -491,11 +491,11 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux); verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness); when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness); @@ -522,10 +522,10 @@ public final class DisplayPowerControllerTest { // The second time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false)); verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux); verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false)); } @Test @@ -560,7 +560,7 @@ public final class DisplayPowerControllerTest { followerListener.onBrightnessChanged(initialFollowerBrightness); advanceTime(1); verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(followerDpc.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -581,11 +581,11 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness); @@ -596,7 +596,7 @@ public final class DisplayPowerControllerTest { mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc); advanceTime(1); verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); when(followerDpc.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -614,10 +614,11 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); - verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat()); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat(), + anyBoolean()); verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test @@ -661,9 +662,9 @@ public final class DisplayPowerControllerTest { secondFollowerListener.onBrightnessChanged(initialFollowerBrightness); advanceTime(1); verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(followerHolder.displayPowerState.getScreenBrightness()) .thenReturn(initialFollowerBrightness); @@ -686,11 +687,11 @@ public final class DisplayPowerControllerTest { listener.onBrightnessChanged(brightness); advanceTime(1); // Send messages, run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); @@ -701,9 +702,9 @@ public final class DisplayPowerControllerTest { mHolder.dpc.stop(); advanceTime(1); verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false)); clearInvocations(followerHolder.animator, secondFollowerHolder.animator); } @@ -756,7 +757,7 @@ public final class DisplayPowerControllerTest { verify(mHolder.screenOffBrightnessSensorController, atLeastOnce()) .getAutomaticScreenBrightness(); - verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false)); } @Test @@ -793,7 +794,7 @@ public final class DisplayPowerControllerTest { verify(mHolder.screenOffBrightnessSensorController, atLeastOnce()) .getAutomaticScreenBrightness(); - verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false)); } @Test @@ -1038,7 +1039,8 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged - verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); + verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(), + eq(false)); } @Test @@ -1147,7 +1149,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness); brightness = 0.05f; @@ -1159,7 +1161,7 @@ public final class DisplayPowerControllerTest { // The second time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE), eq(false)); brightness = 0.9f; when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( @@ -1169,7 +1171,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState // The third time, the animation rate should be slow verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE)); + eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE), eq(false)); } @Test @@ -1204,7 +1206,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness); @@ -1218,7 +1220,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_MINIMUM)); + eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false)); } @Test @@ -1242,7 +1244,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE)); + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness); @@ -1255,7 +1257,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness), - eq(BRIGHTNESS_RAMP_RATE_MINIMUM)); + eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false)); } private void advanceTime(long timeMs) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java index 2820da7c49c1..810a8b2c8297 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java @@ -28,6 +28,8 @@ import org.junit.Test; @SmallTest public class RampAnimatorTest { + private static final float FLOAT_TOLERANCE = 0.0000001f; + private RampAnimator mRampAnimator; private final TestObject mTestObject = new TestObject(); @@ -46,16 +48,46 @@ public class RampAnimatorTest { @Before public void setUp() { - mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty); + mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty, () -> 0); } @Test public void testInitialValueUsedInLastAnimationStep() { - mRampAnimator.setAnimationTarget(0.67f, 0.1f); + mRampAnimator.setAnimationTarget(0.67f, 0.1f, false); assertEquals(0.67f, mTestObject.mValue, 0); } + @Test + public void testAnimationStep_respectTimeLimits() { + // animation is limited to 2s + mRampAnimator.setAnimationTimeLimits(2_000, 2_000); + // initial brightness value, applied immediately, in HLG = 0.8716434 + mRampAnimator.setAnimationTarget(0.5f, 0.1f, false); + // expected brightness, in HLG = 0.9057269 + // delta = 0.0340835, duration = 3.40835s > 2s + // new rate = delta/2 = 0.01704175 u/s + mRampAnimator.setAnimationTarget(0.6f, 0.01f, false); + // animation step = 1s, new HGL = 0.88868515 + mRampAnimator.performNextAnimationStep(1_000_000_000); + // converted back to Linear + assertEquals(0.54761934f, mTestObject.mValue, FLOAT_TOLERANCE); + } + + @Test + public void testAnimationStep_ignoreTimeLimits() { + // animation is limited to 2s + mRampAnimator.setAnimationTimeLimits(2_000, 2_000); + // initial brightness value, applied immediately, in HLG = 0.8716434 + mRampAnimator.setAnimationTarget(0.5f, 0.1f, false); + // rate = 0.01f, time limits are ignored + mRampAnimator.setAnimationTarget(0.6f, 0.01f, true); + // animation step = 1s, new HGL = 0.8816434 + mRampAnimator.performNextAnimationStep(1_000_000_000); + // converted back to Linear + assertEquals(0.52739114f, mTestObject.mValue, FLOAT_TOLERANCE); + } + private static class TestObject { private float mValue; } -- GitLab From cb4cb3e53b2a9d060634e8bcfb57df1c2e5f6705 Mon Sep 17 00:00:00 2001 From: Hakjun Choi Date: Fri, 22 Sep 2023 00:12:02 +0000 Subject: [PATCH 63/95] Fix Build Breakage - method @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isNtn(); + method @FlaggedApi(com.android.internal.telephony.flags.oem_enabled_satellite_flag) public boolean isNtn(); from api/current.txt Bug: 301517271 Test: Build Test Change-Id: Ib8700d1c830f4ede76bf0193675fd53e3208a1fb --- core/api/current.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/api/current.txt b/core/api/current.txt index 904b0d21a3e3..1eb9e97db42b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -44888,7 +44888,7 @@ package android.telephony { method public int getSubscriptionType(); method public int getUsageSetting(); method public boolean isEmbedded(); - method @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isNtn(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isNtn(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; -- GitLab From 65f8b76112bbe0674f2722b0517938827bc34ca0 Mon Sep 17 00:00:00 2001 From: Jihoon Kang Date: Fri, 22 Sep 2023 00:30:24 +0000 Subject: [PATCH 64/95] RESTRICT AUTOMERGE Move java_api_library modules to f/b/api/StubLibraries.bp This change was already submitted with https://android-review.git.corp.google.com/q/topic:%22revert-2713677-revert-2655262-move_java_api_libraries-JTESUMBERD-FPSEKJYXCE%22 but was never propagated to downstream. Test: m nothing --build-from-text-stub Change-Id: I0d107ceb59cc53ec6663145796f4e4b0b74f2dc9 --- api/StubLibraries.bp | 164 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 9a0053f8add6..c1edfd2c2920 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -572,6 +572,170 @@ java_genrule { }, } +// +// Java API defaults and libraries for single tree build +// + +java_defaults { + name: "stub-annotation-defaults", + libs: [ + "stub-annotations", + ], + static_libs: [ + // stub annotations do not contribute to the API surfaces but are statically + // linked in the stubs for API surfaces (see frameworks/base/StubLibraries.bp). + // This is because annotation processors insist on loading the classes for any + // annotations found, thus should exist inside android.jar. + "private-stub-annotations-jar", + ], +} + +// Listing of API domains contribution and dependencies per API surfaces +java_defaults { + name: "android_test_stubs_current_contributions", + api_surface: "test", + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + "framework-virtualization.stubs.source.test.api.contribution", + ], +} + +java_defaults { + name: "android_test_frameworks_core_stubs_current_contributions", + api_surface: "test", + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + ], +} + +java_defaults { + name: "android_module_lib_stubs_current_contributions", + api_surface: "module-lib", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + "art.module.public.api.stubs.source.api.contribution", + "art.module.public.api.stubs.source.system.api.contribution", + "art.module.public.api.stubs.source.module_lib.api.contribution", + "i18n.module.public.api.stubs.source.api.contribution", + "i18n.module.public.api.stubs.source.system.api.contribution", + "i18n.module.public.api.stubs.source.module_lib.api.contribution", + ], +} + +// Java API library definitions per API surface +java_api_library { + name: "android_stubs_current.from-text", + api_surface: "public", + defaults: [ + // This module is dynamically created at frameworks/base/api/api.go + // instead of being written out, in order to minimize edits in the codebase + // when there is a change in the list of modules. + // that contributes to an api surface. + "android_stubs_current_contributions", + "stub-annotation-defaults", + ], + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + ], + visibility: ["//visibility:public"], +} + +java_api_library { + name: "android_system_stubs_current.from-text", + api_surface: "system", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "stub-annotation-defaults", + ], + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + ], + visibility: ["//visibility:public"], +} + +java_api_library { + name: "android_test_stubs_current.from-text", + api_surface: "test", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_stubs_current_contributions", + "stub-annotation-defaults", + ], + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + ], + visibility: ["//visibility:public"], +} + +java_api_library { + name: "android_test_frameworks_core_stubs_current.from-text", + api_surface: "test", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_frameworks_core_stubs_current_contributions", + "stub-annotation-defaults", + ], + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + ], +} + +java_api_library { + name: "android_module_lib_stubs_current_full.from-text", + api_surface: "module-lib", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_module_lib_stubs_current_contributions_full", + ], + libs: [ + "stub-annotations", + ], + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + ], + visibility: ["//visibility:public"], +} + +java_api_library { + name: "android_module_lib_stubs_current.from-text", + api_surface: "module-lib", + defaults: [ + "android_module_lib_stubs_current_contributions", + ], + libs: [ + "android_module_lib_stubs_current_full.from-text", + "stub-annotations", + ], + visibility: ["//visibility:public"], +} + +java_api_library { + name: "android_system_server_stubs_current.from-text", + api_surface: "system-server", + api_contributions: [ + "services-non-updatable-stubs.api.contribution", + ], + libs: [ + "android_module_lib_stubs_current.from-text", + "stub-annotations", + ], + static_libs: [ + "android_module_lib_stubs_current.from-text", + ], + visibility: ["//visibility:public"], +} + //////////////////////////////////////////////////////////////////////// // api-versions.xml generation, for public and system. This API database // also contains the android.test.* APIs. -- GitLab From 0c9b699e79fce12e0f853cd063be9f93aad3ce36 Mon Sep 17 00:00:00 2001 From: Matt Walliser Date: Thu, 21 Sep 2023 22:57:59 +0000 Subject: [PATCH 65/95] Clean up SharedConnectivityManager on unbind. mService is used by registerCallback as an indication of if the service is bound to. This was only being set to null when the service was unbound from the service side, not when unbound from the manager side. Bug: 301296167 Test: atest -c android.net.wifi.sharedconnectivity.cts.app.SharedConnectivityManagerTest Change-Id: I2ef94cf0489b41a48a4279fbe3f9041ae0271280 --- .../wifi/sharedconnectivity/app/SharedConnectivityManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index bc41829f16d2..71ac94ba2ee4 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -334,6 +334,7 @@ public class SharedConnectivityManager { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); mServiceConnection = null; + mService = null; } } -- GitLab From 039334381ea0bd19fcad15d1940af98d0ad51428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Thu, 21 Sep 2023 16:44:52 +1000 Subject: [PATCH 66/95] lint_fix: fix CLI arguments use Commit df4cd065 updated how command line arguments are parsed. However, sys.argv[0] should be ignored (as this is the name of the program). Remove the argument, so the default value (sys.argv[1:]) is used. Also, use the embedded_launcher so that the help renders as: usage: lint_fix [-h] instead of: usage: usage: __soong_entrypoint_redirector__.py [-h] Test: lint_fix --print --no-fix --check AnnotatedAidlCounter \ --lint-module AndroidUtilsLintChecker services.autofill Change-Id: I00e3e3b70c9715e363d3448ae84bf9ff161f2306 --- tools/lint/fix/Android.bp | 5 +++++ tools/lint/fix/soong_lint_fix.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp index 43f21221ae5a..ddacf57c3a3e 100644 --- a/tools/lint/fix/Android.bp +++ b/tools/lint/fix/Android.bp @@ -25,6 +25,11 @@ python_binary_host { name: "lint_fix", main: "soong_lint_fix.py", srcs: ["soong_lint_fix.py"], + version: { + py3: { + embedded_launcher: true, + }, + }, } python_library_host { diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py index b42f9eec99d0..4a2e37e51d71 100644 --- a/tools/lint/fix/soong_lint_fix.py +++ b/tools/lint/fix/soong_lint_fix.py @@ -213,5 +213,5 @@ def _setup_parser(): if __name__ == "__main__": opts = SoongLintFixOptions() - opts.parse_args(sys.argv) + opts.parse_args() SoongLintFix(opts).run() -- GitLab From 213e577bdd2b8e46e51c5863dcec1d8a5d649735 Mon Sep 17 00:00:00 2001 From: Diego Vela Date: Mon, 11 Sep 2023 22:06:41 +0000 Subject: [PATCH 67/95] Report folding features to letterboxed apps. Letterboxed apps lost support for Folding Features. We are enabling reporting folding features to letterboxed apps again. Report folding features if an Activity is embedded or not in PiP. Bug: 295785410 Test: atest CtsWindowManagerJetpackTestCases Change-Id: Ib964b22278c31982a3d6bf66abaab3dac0c4093b --- .../extensions/WindowExtensionsImpl.java | 9 +--- .../layout/WindowLayoutComponentImpl.java | 46 +++---------------- 2 files changed, 7 insertions(+), 48 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index c3d8f9a99d79..a663f9fafb50 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -20,7 +20,6 @@ import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.Application; import android.content.Context; -import android.window.TaskFragmentOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -83,13 +82,7 @@ public class WindowExtensionsImpl implements WindowExtensions { Context context = getApplication(); DeviceStateManagerFoldingFeatureProducer producer = getFoldingFeatureProducer(); - // TODO(b/263263909) Use the organizer to tell if an Activity is embededed. - // Need to improve our Dependency Injection and centralize the logic. - TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> { - throw new RuntimeException("Not allowed!"); - }); - mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer, - producer); + mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index ba57b76020b4..e0942b74e0a2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -17,6 +17,7 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; + import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.util.ExtensionHelper.isZero; @@ -24,7 +25,6 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.app.Activity; -import android.app.ActivityClient; import android.app.Application; import android.app.WindowConfiguration; import android.content.ComponentCallbacks; @@ -34,8 +34,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.view.WindowManager; -import android.window.TaskFragmentOrganizer; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; @@ -51,7 +49,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; /** @@ -85,16 +82,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Map, Consumer> mJavaToExtConsumers = new ArrayMap<>(); - private final TaskFragmentOrganizer mTaskFragmentOrganizer; - public WindowLayoutComponentImpl(@NonNull Context context, - @NonNull TaskFragmentOrganizer taskFragmentOrganizer, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); mFoldingFeatureProducer = foldingFeatureProducer; mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - mTaskFragmentOrganizer = taskFragmentOrganizer; } /** @@ -374,38 +367,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { // Display features are not supported on secondary displays. return false; } - final int windowingMode; - IBinder activityToken = context.getActivityToken(); - if (activityToken != null) { - final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration( - activityToken); - if (taskConfig == null) { - // If we cannot determine the task configuration for any reason, it is likely that - // we won't be able to determine its position correctly as well. DisplayFeatures' - // bounds in this case can't be computed correctly, so we should skip. - return false; - } - final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); - final WindowManager windowManager = Objects.requireNonNull( - context.getSystemService(WindowManager.class)); - final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds(); - boolean isTaskExpanded = maxBounds.equals(taskBounds); - /* - * We need to proxy being in full screen because when a user enters PiP and exits PiP - * the task windowingMode will report multi-window/pinned until the transition is - * finished in WM Shell. - * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen - */ - return isTaskExpanded; - } else { - // TODO(b/242674941): use task windowing mode for window context that associates with - // activity. - windowingMode = context.getResources().getConfiguration().windowConfiguration - .getWindowingMode(); - } - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return !WindowConfiguration.inMultiWindowMode(windowingMode); + + // We do not report folding features for Activities in PiP because the bounds are + // not updated fast enough and the window is too small for the UI to adapt. + return context.getResources().getConfiguration().windowConfiguration + .getWindowingMode() != WindowConfiguration.WINDOWING_MODE_PINNED; } @GuardedBy("mLock") -- GitLab From 5a7a5946dceb80509e65d3274cd82933ec67fcc7 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 21 Sep 2023 22:52:58 +0000 Subject: [PATCH 68/95] Enable Rotary Scroll haptics for Wear Bug: 299587011 Change-Id: I335056bd3253824932840a6baf41a601af0e3799 Test: manual --- core/res/res/values-watch/config.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 4027f5c78035..af305329da1a 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -41,6 +41,10 @@ measured in dips per second. Setting this to -1dp disables rotary encoder fling. --> 8000dp + + true + 1 -- GitLab From 2249f626e4b12b8e08cfbafac6ae1b6b24a44170 Mon Sep 17 00:00:00 2001 From: Charlotte Lu Date: Tue, 19 Sep 2023 19:13:14 +0800 Subject: [PATCH 69/95] Add SettingsExposedDropdownMenuCheckBox. SettingsExposedDropdownMenuCheckBox requires input index. Bug: 298906796 Test: Munual Change-Id: I6059bd5371a2472fbbb9a6b0aa83be769f90264e --- ...tingsExposedDropdownMenuCheckBoxProvider.kt | 2 +- .../SettingsExposedDropdownMenuCheckBox.kt | 18 ++++++++++-------- .../SettingsExposedDropdownMenuCheckBoxTest.kt | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt index 37c8eef8a90d..d28964676bdd 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt @@ -37,7 +37,7 @@ object SettingsExposedDropdownMenuCheckBoxProvider : SettingsPageProvider { override val name = "SettingsExposedDropdownMenuCheckBox" private const val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel" private val options = listOf("item1", "item2", "item3") - private val selectedOptionsState1 = mutableStateListOf("item1", "item2") + private val selectedOptionsState1 = mutableStateListOf(0, 1) override fun getTitle(arguments: Bundle?): String { return TITLE diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt index a25818553c05..5d248e192c7a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt @@ -51,7 +51,8 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme fun SettingsExposedDropdownMenuCheckBox( label: String, options: List, - selectedOptionsState: SnapshotStateList, + selectedOptionsState: SnapshotStateList, + emptyVal: String = "", enabled: Boolean, onSelectedOptionStateChange: () -> Unit, ) { @@ -70,7 +71,8 @@ fun SettingsExposedDropdownMenuCheckBox( modifier = Modifier .menuAnchor() .fillMaxWidth(), - value = selectedOptionsState.joinToString(", "), + value = if (selectedOptionsState.size == 0) emptyVal + else selectedOptionsState.joinToString { options[it] }, onValueChange = {}, label = { Text(text = label) }, trailingIcon = { @@ -89,19 +91,19 @@ fun SettingsExposedDropdownMenuCheckBox( .width(with(LocalDensity.current) { dropDownWidth.toDp() }), onDismissRequest = { expanded = false }, ) { - options.forEach { option -> + options.forEachIndexed { index, option -> TextButton( modifier = Modifier .fillMaxHeight() .fillMaxWidth(), onClick = { - if (selectedOptionsState.contains(option)) { + if (selectedOptionsState.contains(index)) { selectedOptionsState.remove( - option + index ) } else { selectedOptionsState.add( - option + index ) } onSelectedOptionStateChange() @@ -114,7 +116,7 @@ fun SettingsExposedDropdownMenuCheckBox( verticalAlignment = Alignment.CenterVertically ) { Checkbox( - checked = selectedOptionsState.contains(option), + checked = selectedOptionsState.contains(index), onCheckedChange = null, ) Text(text = option) @@ -130,7 +132,7 @@ fun SettingsExposedDropdownMenuCheckBox( @Composable private fun ActionButtonsPreview() { val options = listOf("item1", "item2", "item3") - val selectedOptionsState = remember { mutableStateListOf("item1", "item2") } + val selectedOptionsState = remember { mutableStateListOf(0, 1) } SettingsTheme { SettingsExposedDropdownMenuCheckBox( label = "label", diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt index b0271ae1d98c..2b78ed7d6fa2 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt @@ -39,7 +39,7 @@ class SettingsExposedDropdownMenuCheckBoxTest { private val item2 = "item2" private val item3 = "item3" private val options = listOf(item1, item2, item3) - private val selectedOptionsState1 = mutableStateListOf(item1, item2) + private val selectedOptionsState1 = mutableStateListOf(0, 1) private val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel" @Test -- GitLab From 1582f4833b60d42716382202b9e7844f96357d57 Mon Sep 17 00:00:00 2001 From: Diego Vela Date: Fri, 15 Sep 2023 21:27:06 +0000 Subject: [PATCH 70/95] Add raw configuration change listener updates. Add raw configuration change listener updates. Letterboxed Activities do not receive a configuration update when they are repositioned. Listening to all configuration changes will correctly update folding features. Change exceptions from hard exceptions to Log.wtf so that we do not crash on production apps. Bug: 295785410 Test: Open Samples and open the slim (letterbox) Activities. Change-Id: Ia079d06a403a59bb0f1eafdaad6ce238749a2af2 --- core/java/android/app/ActivityThread.java | 24 ++++ ...onfigurationChangedListenerController.java | 116 ++++++++++++++++++ .../layout/WindowLayoutComponentImpl.java | 39 +++++- 3 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 core/java/android/app/ConfigurationChangedListenerController.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 01912012f04a..c51dab7f7865 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -36,6 +36,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; @@ -265,6 +266,7 @@ import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * This manages the execution of the main thread in an @@ -382,6 +384,11 @@ public final class ActivityThread extends ClientTransactionHandler @GuardedBy("mAppThread") private int mLastProcessState = PROCESS_STATE_UNKNOWN; final ArrayList> mLastAssistStructures = new ArrayList<>(); + + @NonNull + private final ConfigurationChangedListenerController mConfigurationChangedListenerController = + new ConfigurationChangedListenerController(); + private int mLastSessionId; // Holds the value of the last reported device ID value from the server for the top activity. int mLastReportedDeviceId; @@ -3608,6 +3615,21 @@ public final class ActivityThread extends ClientTransactionHandler return mConfigurationController.getConfiguration(); } + /** + * @hide + */ + public void addConfigurationChangedListener(Executor executor, + Consumer consumer) { + mConfigurationChangedListenerController.addListener(executor, consumer); + } + + /** + * @hide + */ + public void removeConfigurationChangedListener(Consumer consumer) { + mConfigurationChangedListenerController.removeListener(consumer); + } + @Override public void updatePendingConfiguration(Configuration config) { final Configuration updatedConfig = @@ -6252,6 +6274,8 @@ public final class ActivityThread extends ClientTransactionHandler " did not call through to super.onConfigurationChanged()"); } } + mConfigurationChangedListenerController + .dispatchOnConfigurationChanged(activity.getActivityToken()); return configToReport; } diff --git a/core/java/android/app/ConfigurationChangedListenerController.java b/core/java/android/app/ConfigurationChangedListenerController.java new file mode 100644 index 000000000000..c644d57fb9a7 --- /dev/null +++ b/core/java/android/app/ConfigurationChangedListenerController.java @@ -0,0 +1,116 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.os.IBinder; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Manages listeners for unfiltered configuration changes. + * @hide + */ +class ConfigurationChangedListenerController { + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final List mListenerContainers = new ArrayList<>(); + + /** + * Adds a listener to receive updates when they are dispatched. This only dispatches updates and + * does not relay the last emitted value. If called with the same listener then this method does + * not have any effect. + * @param executor an executor that is used to dispatch the updates. + * @param consumer a listener interested in receiving updates. + */ + void addListener(@NonNull Executor executor, + @NonNull Consumer consumer) { + synchronized (mLock) { + if (indexOf(consumer) > -1) { + return; + } + mListenerContainers.add(new ListenerContainer(executor, consumer)); + } + } + + /** + * Removes the listener that was previously registered. If the listener was not registered this + * method does not have any effect. + */ + void removeListener(@NonNull Consumer consumer) { + synchronized (mLock) { + final int index = indexOf(consumer); + if (index > -1) { + mListenerContainers.remove(index); + } + } + } + + /** + * Dispatches the update to all registered listeners + * @param activityToken a token for the {@link Activity} that received a configuration update. + */ + void dispatchOnConfigurationChanged(@NonNull IBinder activityToken) { + final List consumers; + synchronized (mLock) { + consumers = new ArrayList<>(mListenerContainers); + } + for (int i = 0; i < consumers.size(); i++) { + consumers.get(i).accept(activityToken); + } + } + + @GuardedBy("mLock") + private int indexOf(Consumer consumer) { + for (int i = 0; i < mListenerContainers.size(); i++) { + if (mListenerContainers.get(i).isMatch(consumer)) { + return i; + } + } + return -1; + } + + private static final class ListenerContainer { + + @NonNull + private final Executor mExecutor; + @NonNull + private final Consumer mConsumer; + + ListenerContainer(@NonNull Executor executor, + @NonNull Consumer consumer) { + mExecutor = executor; + mConsumer = consumer; + } + + public boolean isMatch(@NonNull Consumer consumer) { + return mConsumer.equals(consumer); + } + + public void accept(@NonNull IBinder activityToken) { + mExecutor.execute(() -> mConsumer.accept(activityToken)); + } + + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index e0942b74e0a2..9b84a48cdbda 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -25,6 +25,7 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.app.Activity; +import android.app.ActivityThread; import android.app.Application; import android.app.WindowConfiguration; import android.content.ComponentCallbacks; @@ -34,6 +35,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; @@ -60,7 +62,7 @@ import java.util.Set; * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. */ public class WindowLayoutComponentImpl implements WindowLayoutComponent { - private static final String TAG = "SampleExtension"; + private static final String TAG = WindowLayoutComponentImpl.class.getSimpleName(); private final Object mLock = new Object(); @@ -82,6 +84,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Map, Consumer> mJavaToExtConsumers = new ArrayMap<>(); + private final RawConfigurationChangedListener mRawConfigurationChangedListener = + new RawConfigurationChangedListener(); + public WindowLayoutComponentImpl(@NonNull Context context, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { ((Application) context.getApplicationContext()) @@ -102,6 +107,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer extConsumer = consumer::accept; synchronized (mLock) { mJavaToExtConsumers.put(consumer, extConsumer); + updateListenerRegistrations(); } addWindowLayoutInfoListener(activity, extConsumer); } @@ -155,6 +161,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer extConsumer; synchronized (mLock) { extConsumer = mJavaToExtConsumers.remove(consumer); + updateListenerRegistrations(); } if (extConsumer != null) { removeWindowLayoutInfoListener(extConsumer); @@ -184,6 +191,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } + @GuardedBy("mLock") + private void updateListenerRegistrations() { + ActivityThread currentThread = ActivityThread.currentActivityThread(); + if (mJavaToExtConsumers.isEmpty()) { + currentThread.removeConfigurationChangedListener(mRawConfigurationChangedListener); + } else { + currentThread.addConfigurationChangedListener(Runnable::run, + mRawConfigurationChangedListener); + } + } + @GuardedBy("mLock") @NonNull private Set getContextsListeningForLayoutChanges() { @@ -329,25 +347,28 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { continue; } if (featureRect.left != 0 && featureRect.top != 0) { - throw new IllegalArgumentException("Bounding rectangle must start at the top or " + Log.wtf(TAG, "Bounding rectangle must start at the top or " + "left of the window. BaseFeatureRect: " + baseFeature.getRect() + ", FeatureRect: " + featureRect + ", WindowConfiguration: " + windowConfiguration); + continue; } if (featureRect.left == 0 && featureRect.width() != windowConfiguration.getBounds().width()) { - throw new IllegalArgumentException("Horizontal FoldingFeature must have full width." + Log.wtf(TAG, "Horizontal FoldingFeature must have full width." + " BaseFeatureRect: " + baseFeature.getRect() + ", FeatureRect: " + featureRect + ", WindowConfiguration: " + windowConfiguration); + continue; } if (featureRect.top == 0 && featureRect.height() != windowConfiguration.getBounds().height()) { - throw new IllegalArgumentException("Vertical FoldingFeature must have full height." + Log.wtf(TAG, "Vertical FoldingFeature must have full height." + " BaseFeatureRect: " + baseFeature.getRect() + ", FeatureRect: " + featureRect + ", WindowConfiguration: " + windowConfiguration); + continue; } features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } @@ -400,6 +421,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } + private final class RawConfigurationChangedListener implements + java.util.function.Consumer { + @Override + public void accept(IBinder activityToken) { + synchronized (mLock) { + onDisplayFeaturesChangedIfListening(activityToken); + } + } + } + private final class ConfigurationChangeListener implements ComponentCallbacks { final IBinder mToken; -- GitLab From 1ef586cbc7d28a23c20695c3d1be58e5832d56fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Thu, 21 Sep 2023 08:34:15 +0000 Subject: [PATCH 71/95] Disable bigpicture lazy loading for unsupported icons Don't set placeholders for bitmap or data based icons inside BigPictureStyle Notifications, because it doesn't save on memory. Bug: 283082473 Test: atest BigPictureIconManager Change-Id: I547668b5b35cf1f9932eee77d941b69d9e023e38 --- .../notification/row/BigPictureIconManager.kt | 16 +++++- .../row/BigPictureIconManagerTest.kt | 53 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt index 88dbb4cf1342..ba1e6dd8ca4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt @@ -89,6 +89,7 @@ constructor( this.lastLoadingJob?.cancel() this.lastLoadingJob = when { + skipLazyLoading(state.icon) -> null state is Empty && shown -> state.icon?.let(::startLoadingJob) state is PlaceHolder && shown -> startLoadingJob(state.icon) state is FullImage && !shown -> @@ -144,7 +145,7 @@ constructor( private fun loadImageOrPlaceHolderSync(icon: Icon?): Drawable? { icon ?: return null - if (viewShown) { + if (viewShown || skipLazyLoading(icon)) { return loadImageSync(icon) } @@ -228,6 +229,19 @@ constructor( } ) + /** + * We don't support lazy-loading or set placeholders for bitmap and data based icons, because + * they gonna stay in memory anyways. + */ + private fun skipLazyLoading(icon: Icon?): Boolean = + when (icon?.type) { + Icon.TYPE_BITMAP, + Icon.TYPE_ADAPTIVE_BITMAP, + Icon.TYPE_DATA, + null -> true + else -> false + } + private fun log(msg: String) { if (DEBUG) { Log.d(TAG, "$msg state=${getDebugString()}") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt index c650e01263bc..e7bcf3f0201f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -70,7 +70,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { } private val unsupportedIcon by lazy { Icon.createWithBitmap( - BitmapFactory.decodeResource(context.resources, R.drawable.dessert_zombiegingerbread) + BitmapFactory.decodeResource(context.resources, R.drawable.dessert_donutburger) ) } private val invalidIcon by lazy { Icon.createWithContentUri(Uri.parse("this.is/broken")) } @@ -100,7 +100,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { } @Test - fun onIconUpdated_notSupportedType_fullImageLoaded() = + fun onIconUpdated_unsupportedType_fullImageLoaded() = testScope.runTest { // WHEN update with an unsupported icon iconManager.updateIcon(mockConsumer, unsupportedIcon).run() @@ -153,6 +153,24 @@ class BigPictureIconManagerTest : SysuiTestCase() { } @Test + fun onIconUpdated_iconAlreadySetForUnsupportedIcon_loadsNewIcon() = + testScope.runTest { + // GIVEN an unsupported icon is set + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + // AND the view is shown + iconManager.onViewShown(true) + reset(mockConsumer) + + // WHEN a new icon is set + iconManager.updateIcon(mockConsumer, supportedIcon).run() + + // THEN consumer is updated with the new image + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsFullImage(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test fun onIconUpdated_supportedTypeButTooWide_resizedPlaceholderLoaded() = testScope.runTest { // GIVEN the max width is smaller than our image @@ -249,12 +267,10 @@ class BigPictureIconManagerTest : SysuiTestCase() { verifyZeroInteractions(mockConsumer) } - // nice to have tests - @Test - fun onViewShown_fullImageLoaded_nothingHappens() = + fun onViewShown_unsupportedIconLoaded_nothingHappens() = testScope.runTest { - // GIVEN full image is showing + // GIVEN full image is showing for an unsupported icon iconManager.updateIcon(mockConsumer, unsupportedIcon).run() reset(mockConsumer) @@ -266,11 +282,31 @@ class BigPictureIconManagerTest : SysuiTestCase() { verifyZeroInteractions(mockConsumer) } + @Test + fun onViewHidden_unsupportedIconLoadedAndViewIsShown_nothingHappens() = + testScope.runTest { + // GIVEN full image is showing for an unsupported icon + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + // AND the view is shown + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + + // WHEN the view goes off the screen + iconManager.onViewShown(false) + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + @Test fun onViewHidden_placeholderShowing_nothingHappens() = testScope.runTest { // GIVEN placeholder image is showing - iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + iconManager.updateIcon(mockConsumer, supportedIcon).run() reset(mockConsumer) // WHEN the view is hidden @@ -296,6 +332,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { iconManager.onViewShown(true) runCurrent() + // THEN nothing happens verifyZeroInteractions(mockConsumer) } @@ -303,7 +340,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { fun onViewHidden_alreadyHidden_nothingHappens() = testScope.runTest { // GIVEN placeholder image is showing and the view is hidden - iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + iconManager.updateIcon(mockConsumer, supportedIcon).run() iconManager.onViewShown(false) advanceTimeBy(FREE_IMAGE_DELAY_MS) runCurrent() -- GitLab From f036bb66357dcdc6e2cf07ea81dee7955fd48025 Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Mon, 18 Sep 2023 14:23:17 -0700 Subject: [PATCH 72/95] [framweweworks] clean up broadcast receivers after test BUG: 298063459 Test: presubmit Change-Id: Id0f728120fe4db6b86f9e753f3eaa17780aa5db0 Merged-In: Id0f728120fe4db6b86f9e753f3eaa17780aa5db0 --- .../src/android/content/BroadcastReceiverTests.java | 12 +++++++++++- .../android/internal/os/BatteryInputSuspendTest.java | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java index 5dbeac2f32e9..407c6c3e2e2c 100644 --- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java +++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java @@ -26,6 +26,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest public class BroadcastReceiverTests { @@ -47,15 +50,22 @@ public class BroadcastReceiverTests { @Test public void testReceiverLimit() { final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction"); + final List receivers = new ArrayList<>(RECEIVER_LIMIT_PER_APP); try { for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) { - mContext.registerReceiver(new EmptyReceiver(), mockFilter, + final EmptyReceiver receiver = new EmptyReceiver(); + mContext.registerReceiver(receiver, mockFilter, Context.RECEIVER_EXPORTED_UNAUDITED); + receivers.add(receiver); } fail("No exception thrown when registering " + (RECEIVER_LIMIT_PER_APP + 1) + " receivers"); } catch (IllegalStateException ise) { // Expected + } finally { + for (int i = receivers.size() - 1; i >= 0; i--) { + mContext.unregisterReceiver(receivers.remove(i)); + } } } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java index e870d6022058..8d825e4deb81 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java @@ -99,6 +99,7 @@ public class BatteryInputSuspendTest { if (isCharging(intent) == mExpectedChargingState) { mReady.open(); } + context.unregisterReceiver(this); } }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } -- GitLab From 3b61ee74224d24d0597ecfc1da87e7f65d37b835 Mon Sep 17 00:00:00 2001 From: axfordjc Date: Thu, 21 Sep 2023 09:45:17 +0000 Subject: [PATCH 73/95] is_small_screen_landscape resource Intended to be used alongside the flag "lockscreen.enable_landscape" (b/293252410) Bug: 293252410 Test: None Change-Id: I02ba5884780eb511dac3ba5cdfbfbca0e6420f56 --- packages/SystemUI/res/values-land/bools.xml | 3 +++ packages/SystemUI/res/values-sw600dp-land/bools.xml | 3 +++ packages/SystemUI/res/values/bools.xml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/SystemUI/res/values-land/bools.xml b/packages/SystemUI/res/values-land/bools.xml index e24792dc7dd7..c112edcf22bf 100644 --- a/packages/SystemUI/res/values-land/bools.xml +++ b/packages/SystemUI/res/values-land/bools.xml @@ -19,4 +19,7 @@ true + + + true diff --git a/packages/SystemUI/res/values-sw600dp-land/bools.xml b/packages/SystemUI/res/values-sw600dp-land/bools.xml index c4d77e894141..36926a2b4813 100644 --- a/packages/SystemUI/res/values-sw600dp-land/bools.xml +++ b/packages/SystemUI/res/values-sw600dp-land/bools.xml @@ -19,4 +19,7 @@ false + + + false diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml index 39566622a5f4..234c6df69d0b 100644 --- a/packages/SystemUI/res/values/bools.xml +++ b/packages/SystemUI/res/values/bools.xml @@ -63,4 +63,7 @@ false + + + false -- GitLab From bdf9641f553380abff35ae95e3ff8cf068833db4 Mon Sep 17 00:00:00 2001 From: axfordjc Date: Sat, 9 Sep 2023 18:14:51 +0000 Subject: [PATCH 74/95] Wider notifications on landscape lockscreen Guarded by flag lockscreen.enable_landscape, skinny notifications are no longer used on small landscape screens so notifications will take up the full width of the notification stack. This is required for small landscape lockscreen feature. Bug: 296571001 Bug: 293252410 Test: NotificationStackScrollLayoutTest Change-Id: I469ab15eed507ef2c4e4d1b8c4d565dd44051a2f --- .../stack/NotificationStackScrollLayout.java | 15 ++++++++++++++- .../stack/NotificationStackScrollLayoutTest.java | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e8521d1673cf..757ae6d9bd80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -569,6 +569,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mShouldUseSplitNotificationShade; private boolean mHasFilteredOutSeenNotifications; @Nullable private SplitShadeStateController mSplitShadeStateController = null; + private boolean mIsSmallLandscapeLockscreenEnabled = false; /** Pass splitShadeStateController to view and update split shade */ public void passSplitShadeStateController(SplitShadeStateController splitShadeStateController) { @@ -628,6 +629,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable super(context, attrs, 0, 0); Resources res = getResources(); FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); + mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled( + Flags.LOCKSCREEN_ENABLE_LANDSCAPE); mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); @@ -1054,6 +1057,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mOverflingDistance = configuration.getScaledOverflingDistance(); Resources res = context.getResources(); + boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled + && res.getBoolean(R.bool.is_small_screen_landscape); + // TODO (b/293252410) remove condition here when flag is launched + // Instead update the config_skinnyNotifsInLandscape to be false whenever + // is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape. + if (useSmallLandscapeLockscreenResources) { + mSkinnyNotifsInLandscape = false; + } else { + mSkinnyNotifsInLandscape = res.getBoolean( + R.bool.config_skinnyNotifsInLandscape); + } mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mStackScrollAlgorithm.initView(context); mAmbientState.reload(context); @@ -1065,7 +1079,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mBottomPadding = res.getDimensionPixelSize(R.dimen.notification_panel_padding_bottom); mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal); - mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape); mSidePaddings = mMinimumPaddings; // Updated in onMeasure by updateSidePadding() mMinInteractionHeight = res.getDimensionPixelSize( R.dimen.notification_min_interaction_height); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 4307066e2d44..036a27c3831a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -154,6 +154,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault()); mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false); mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false); + mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); // Register the feature flags we use // TODO: Ideally we wouldn't need to set these unless a test actually reads them, -- GitLab From 37092e449ae3d89921668bbd641e5184a497395b Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Fri, 22 Sep 2023 10:29:44 +0200 Subject: [PATCH 75/95] Compat changes for VDM display flags Fix: 300905478 Test: presubmit Change-Id: Ie681a97bb75c0797d3d34c4c368316051025d99c --- .../companion/virtual/VirtualDeviceImpl.java | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 3b13410720af..f328b22352ae 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -37,6 +37,7 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; +import android.app.compat.CompatChanges; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -51,6 +52,8 @@ import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; @@ -75,6 +78,7 @@ import android.hardware.input.VirtualNavigationTouchpadConfig; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; @@ -115,12 +119,33 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; + /** + * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent + * with virtual displays created via {@link DisplayManager} and allow for the creation of + * private, auto-mirror, and fixed orientation displays since + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. + * + * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC + * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER = + 294837146L; + private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; + private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC = + DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; + private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:"; /** @@ -130,6 +155,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final Object mVirtualDeviceLock = new Object(); + private final int mBaseVirtualDisplayFlags; + private final Context mContext; private final AssociationInfo mAssociationInfo; private final VirtualDeviceManagerService mService; @@ -311,6 +338,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ? mParams.getBlockedActivities() : mParams.getAllowedActivities(); } + + int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; + if (!CompatChanges.isChangeEnabled( + MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER, mOwnerUid)) { + flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC; + } + if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; + } + mBaseVirtualDisplayFlags = flags; } @VisibleForTesting @@ -323,11 +360,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub * device. */ int getBaseVirtualDisplayFlags() { - int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { - flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; - } - return flags; + return mBaseVirtualDisplayFlags; } /** Returns the camera access controller of this device. */ -- GitLab From 0c5993fa0e39e4959a18994663923bbb2ddc44e1 Mon Sep 17 00:00:00 2001 From: beatricemarch Date: Thu, 31 Aug 2023 12:53:16 +0000 Subject: [PATCH 76/95] Implement the ability to log extras relating to version mismatches between packages on the source and target. Imporve readability of events in dumpsys. Test: manual, do restore on test device, verify that the correct events are printed in the dumpsys atest CtsBackupHostTestCases, GtsBackupHostTestCases atest BackupManagerMonitorDumpsysUtilsTest Bug: 296818666 Change-Id: Ifd2d68861fcd33637c49f0e7802c456e43ffd9a2 --- .../BackupManagerMonitorDumpsysUtils.java | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index 4b0d5a4928b6..797aed9297a3 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -84,15 +84,15 @@ public class BackupManagerMonitorDumpsysUtils { * - Agent logs (if available) * * Example of formatting: - * RESTORE Event: [2023-08-18 17:16:00.735] Agent - Agent logging results - * Package name: com.android.wallpaperbackup - * Agent Logs: - * Data Type: wlp_img_system - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 - * Data Type: wlp_img_lock - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 + * [2023-09-21 14:43:33.824] - Agent logging results + * Package: com.android.wallpaperbackup + * Agent Logs: + * Data Type: wlp_img_system + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 + * Data Type: wlp_img_lock + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 */ public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) { if (isAfterRetentionPeriod()) { @@ -139,17 +139,16 @@ public class BackupManagerMonitorDumpsysUtils { return; } - pw.println("RESTORE Event: [" + timestamp() + "] " + - getCategory(eventCategory) + " - " + - getId(eventId)); + pw.println("[" + timestamp() + "] - " + getId(eventId)); if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)) { - pw.println("\tPackage name: " + pw.println("\tPackage: " + eventBundle.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)); } // TODO(b/296818666): add extras to the events addAgentLogsIfAvailable(eventBundle, pw); + addExtrasIfAvailable(eventBundle, pw); } catch (java.io.IOException e) { Slog.e(TAG, "IO Exception when writing BMM events to file: " + e); } @@ -194,6 +193,37 @@ public class BackupManagerMonitorDumpsysUtils { } } + /** + * Extracts some extras (defined in BackupManagerMonitor as EXTRA_LOG_) + * from the BackupManagerMonitor event. Not all extras have the same importance. For now only + * focus on extras relating to version mismatches between packages on the source and target. + * + * When an event with ID LOG_EVENT_ID_RESTORE_VERSION_HIGHER (trying to restore from higher to + * lower version of a package) parse: + * EXTRA_LOG_RESTORE_VERSION [int]: the version of the package on the source + * EXTRA_LOG_RESTORE_ANYWAY [bool]: if the package allows restore any version + * EXTRA_LOG_RESTORE_VERSION_TARGET [int]: an extra to record the package version on the target + */ + private void addExtrasIfAvailable(Bundle eventBundle, PrintWriter pw) { + if (eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID) == + BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER) { + if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY)) { + pw.println("\t\tPackage supports RestoreAnyVersion: " + + eventBundle.getBoolean(BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY)); + } + if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION)) { + pw.println("\t\tPackage version on source: " + + eventBundle.getLong(BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION)); + } + if (eventBundle.containsKey( + BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)) { + pw.println("\t\tPackage version on target: " + + eventBundle.getLong( + BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)); + } + } + } + /* * Get the path of the text files which stores the BMM events */ -- GitLab From 0f0cdccd74dc93d68fe96caac6563c2ac0ff9833 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Fri, 22 Sep 2023 11:32:11 +0000 Subject: [PATCH 77/95] Ensure keyguard shows, and tells WM Many race conditions can occur, especially with SideFPS. In this case, a successful auth followed by numerous power button pushes, and having distinct home and lockscreen wallpapers, produced a scenario where keyguard was correctly thinking it should be visible but was not always telling WM. This left the device in a state with just a blank screen, behaving in a manner like it was occluded by another activity. Always tell WM that sysui is showing, as there should be no harm in telling it again (just in case). Fixes: 301409430 Fixes: 300899918 Test: race condition - Enable 2 different wallpapers on home/lockscreen, setup sidefps, authenticate, and slam the power button over and over. This is easier to accomplish if you disabling the double-tap camera launch gesture Change-Id: I5c7137dfcf20fb27c697a60df21141d15979e03e --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9241776191d4..f47e6159e817 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3231,7 +3231,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + "mPendingLock=" + mPendingLock + "." + "One of these being false means we re-locked the device during unlock. " + "Do not proceed to finish keyguard exit and unlock."); + doKeyguardLocked(null); finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); + // Ensure WM is notified that we made a decision to show + setShowingLocked(true /* showing */, true /* force */); + return; } -- GitLab From 1063ac01cbd6e0f7b24107152593102b40caa1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Fri, 22 Sep 2023 08:06:08 +0000 Subject: [PATCH 78/95] Make BigPictureIconManager reload the image on each icon update This way we reload the URI every time a BigPicture Notification is reinflated, which means that when the system theme changes the app is given an opportunity to provide a version of the image for the current system theme. This matches the behaviour before the lazy loading. Bug: 283082473 Test: atest BigPictureIconManager Change-Id: I54cf91bd42f0eb760774caf88005150f0a4cec3d --- .../notification/row/BigPictureIconManager.kt | 15 ------ .../row/BigPictureIconManagerTest.kt | 46 ++++++++++++++++++- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt index ba1e6dd8ca4a..a5b32b8d4fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt @@ -120,12 +120,6 @@ constructor( return Runnable {} } - if (displayedState.iconSameAs(icon)) { - // We're already handling this icon, nothing to do here. - log("skipping updateIcon for consumer:$drawableConsumer with icon:$icon") - return Runnable {} - } - this.drawableConsumer = drawableConsumer this.displayedState = Empty(icon) this.lastLoadingJob?.cancel() @@ -256,15 +250,6 @@ constructor( data class PlaceHolder(override val icon: Icon, val drawableSize: Size) : DrawableState(icon) data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon) - - fun iconSameAs(other: Icon?): Boolean { - val displayedIcon = icon - return when { - displayedIcon == null && other == null -> true - displayedIcon != null && other != null -> displayedIcon.sameAs(other) - else -> false - } - } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt index e7bcf3f0201f..311b16cff808 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -111,6 +111,16 @@ class BigPictureIconManagerTest : SysuiTestCase() { assertSize(drawableCaptor.value) } + @Test + fun onIconUpdated_withNull_drawableIsNull() = + testScope.runTest { + // WHEN update with null + iconManager.updateIcon(mockConsumer, null).run() + + // THEN consumer is updated with null + verify(mockConsumer).setImageDrawable(null) + } + @Test fun onIconUpdated_invalidIcon_drawableIsNull() = testScope.runTest { @@ -152,6 +162,24 @@ class BigPictureIconManagerTest : SysuiTestCase() { assertSize(drawableCaptor.value) } + @Test + fun onIconUpdated_iconAlreadySetForTheSameIcon_loadsIconAgain() = + testScope.runTest { + // GIVEN an icon is set + iconManager.updateIcon(mockConsumer, supportedIcon).run() + // AND the view is shown + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + // WHEN the icon is set again + iconManager.updateIcon(mockConsumer, supportedIcon).run() + + // THEN consumer is updated with the new image + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsFullImage(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + @Test fun onIconUpdated_iconAlreadySetForUnsupportedIcon_loadsNewIcon() = testScope.runTest { @@ -159,6 +187,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { iconManager.updateIcon(mockConsumer, unsupportedIcon).run() // AND the view is shown iconManager.onViewShown(true) + runCurrent() reset(mockConsumer) // WHEN a new icon is set @@ -170,7 +199,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { assertSize(drawableCaptor.value) } - @Test + @Test fun onIconUpdated_supportedTypeButTooWide_resizedPlaceholderLoaded() = testScope.runTest { // GIVEN the max width is smaller than our image @@ -282,6 +311,21 @@ class BigPictureIconManagerTest : SysuiTestCase() { verifyZeroInteractions(mockConsumer) } + @Test + fun onViewShown_nullSetForIcon_nothingHappens() = + testScope.runTest { + // GIVEN null is set for the icon + iconManager.updateIcon(mockConsumer, null).run() + reset(mockConsumer) + + // WHEN the view is shown + iconManager.onViewShown(true) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + @Test fun onViewHidden_unsupportedIconLoadedAndViewIsShown_nothingHappens() = testScope.runTest { -- GitLab From ee321a75b81a120b0831e834fed08eb378993e79 Mon Sep 17 00:00:00 2001 From: petsjonkin Date: Mon, 18 Sep 2023 17:21:20 +0000 Subject: [PATCH 79/95] HdrClamper using feature flag and configuration Bug: b/283447291 Test: atest HdrClamperTest Change-Id: I441f2f91ee294c7bb90eca7afaccded64a9ea734 --- .../display/BrightnessRangeController.java | 27 ++++-- .../brightness/clamper/HdrClamper.java | 96 +++++++++++-------- .../display/config/HdrBrightnessData.java | 5 +- .../display/feature/DisplayManagerFlags.java | 8 ++ .../display/feature/display_flags.aconfig | 10 +- .../display/DisplayPowerController2Test.java | 37 ++++++- .../brightness/clamper/HdrClamperTest.java | 16 +++- 7 files changed, 142 insertions(+), 57 deletions(-) diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 21273e0f2785..13ee47eb91c8 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.feature.DisplayManagerFlags; @@ -52,17 +53,26 @@ class BrightnessRangeController { HdrClamper hdrClamper, DisplayManagerFlags flags) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; - mUseHdrClamper = false; - mUseNbmController = flags.isNbmControllerEnabled(); - mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData()); mHdrClamper = hdrClamper; + mUseHdrClamper = flags.isHdrClamperEnabled(); + mUseNbmController = flags.isNbmControllerEnabled(); + if (mUseNbmController) { + mNormalBrightnessModeController.resetNbmData( + displayDeviceConfig.getLuxThrottlingData()); + } + if (mUseHdrClamper) { + mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData()); + } + } void dump(PrintWriter pw) { pw.println("BrightnessRangeController:"); pw.println(" mUseNormalBrightnessController=" + mUseNbmController); + pw.println(" mUseHdrClamper=" + mUseHdrClamper); mHbmController.dump(pw); mNormalBrightnessModeController.dump(pw); + mHdrClamper.dump(pw); } void onAmbientLuxChange(float ambientLux) { @@ -91,6 +101,9 @@ class BrightnessRangeController { displayDeviceConfig::getHdrBrightnessFromSdr); } ); + if (mUseHdrClamper) { + mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData()); + } } void stop() { @@ -128,8 +141,10 @@ class BrightnessRangeController { } float getHdrBrightnessValue() { - float hdrBrightness = mHbmController.getHdrBrightnessValue(); - return Math.min(hdrBrightness, mHdrClamper.getMaxBrightness()); + float hdrBrightness = mHbmController.getHdrBrightnessValue(); + float brightnessMax = mUseHdrClamper ? mHdrClamper.getMaxBrightness() + : PowerManager.BRIGHTNESS_MAX; + return Math.min(hdrBrightness, brightnessMax); } float getTransitionPoint() { @@ -151,6 +166,6 @@ class BrightnessRangeController { } public float getHdrTransitionRate() { - return mHdrClamper.getTransitionRate(); + return mUseHdrClamper ? mHdrClamper.getTransitionRate() : -1; } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java index 079a196234a8..a514136e62a2 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java @@ -16,40 +16,41 @@ package com.android.server.display.brightness.clamper; +import android.annotation.Nullable; import android.os.Handler; import android.os.PowerManager; -import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.config.HdrBrightnessData; -import java.util.HashMap; +import java.io.PrintWriter; import java.util.Map; public class HdrClamper { - private final Configuration mConfiguration = new Configuration(); - private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener; private final Handler mHandler; private final Runnable mDebouncer; - private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + @Nullable + private HdrBrightnessData mHdrBrightnessData = null; - // brightness change speed, in units per seconds, - private float mTransitionRate = -1f; + private float mAmbientLux = Float.MAX_VALUE; + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX; - private float mDesiredTransitionDuration = -1; // in seconds + // brightness change speed, in units per seconds, + private float mTransitionRate = -1f; + private float mDesiredTransitionRate = -1f; public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, Handler handler) { mClamperChangeListener = clamperChangeListener; mHandler = handler; mDebouncer = () -> { - mTransitionRate = Math.abs((mMaxBrightness - mDesiredMaxBrightness) - / mDesiredTransitionDuration); + mTransitionRate = mDesiredTransitionRate; mMaxBrightness = mDesiredMaxBrightness; mClamperChangeListener.onChanged(); }; @@ -65,46 +66,74 @@ public class HdrClamper { return mTransitionRate; } - /** * Updates brightness cap in response to ambient lux change. * Called by ABC in same looper: mHandler.getLooper() */ public void onAmbientLuxChange(float ambientLux) { - float expectedMaxBrightness = findBrightnessLimit(ambientLux); + mAmbientLux = ambientLux; + recalculateBrightnessCap(mHdrBrightnessData, ambientLux); + } + + /** + * Updates brightness cap config. + * Called in same looper: mHandler.getLooper() + */ + public void resetHdrConfig(HdrBrightnessData data) { + mHdrBrightnessData = data; + recalculateBrightnessCap(data, mAmbientLux); + } + + /** + * Dumps the state of HdrClamper. + */ + public void dump(PrintWriter pw) { + pw.println("HdrClamper:"); + pw.println(" mMaxBrightness=" + mMaxBrightness); + pw.println(" mDesiredMaxBrightness=" + mDesiredMaxBrightness); + pw.println(" mTransitionRate=" + mTransitionRate); + pw.println(" mDesiredTransitionRate=" + mDesiredTransitionRate); + pw.println(" mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null" + : mHdrBrightnessData.toString())); + pw.println(" mAmbientLux=" + mAmbientLux); + } + + private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux) { + if (data == null) { + mHandler.removeCallbacks(mDebouncer); + return; + } + float expectedMaxBrightness = findBrightnessLimit(data, ambientLux); + if (mMaxBrightness == expectedMaxBrightness) { mDesiredMaxBrightness = mMaxBrightness; - mDesiredTransitionDuration = -1; + mDesiredTransitionRate = -1f; mTransitionRate = -1f; mHandler.removeCallbacks(mDebouncer); } else if (mDesiredMaxBrightness != expectedMaxBrightness) { mDesiredMaxBrightness = expectedMaxBrightness; long debounceTime; + long transitionDuration; if (mDesiredMaxBrightness > mMaxBrightness) { - debounceTime = mConfiguration.mIncreaseConfig.mDebounceTimeMillis; - mDesiredTransitionDuration = - (float) mConfiguration.mIncreaseConfig.mTransitionTimeMillis / 1000; + debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis; + transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis; } else { - debounceTime = mConfiguration.mDecreaseConfig.mDebounceTimeMillis; - mDesiredTransitionDuration = - (float) mConfiguration.mDecreaseConfig.mTransitionTimeMillis / 1000; + debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis; + transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis; } + mDesiredTransitionRate = Math.abs( + (mMaxBrightness - mDesiredMaxBrightness) * 1000f / transitionDuration); mHandler.removeCallbacks(mDebouncer); mHandler.postDelayed(mDebouncer, debounceTime); } } - @VisibleForTesting - Configuration getConfiguration() { - return mConfiguration; - } - - private float findBrightnessLimit(float ambientLux) { + private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) { float foundAmbientBoundary = Float.MAX_VALUE; float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; for (Map.Entry brightnessPoint : - mConfiguration.mMaxBrightnessLimits.entrySet()) { + data.mMaxBrightnessLimits.entrySet()) { float ambientBoundary = brightnessPoint.getKey(); // find ambient lux upper boundary closest to current ambient lux if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) { @@ -114,19 +143,4 @@ public class HdrClamper { } return foundMaxBrightness; } - - @VisibleForTesting - static class Configuration { - final Map mMaxBrightnessLimits = new HashMap<>(); - final TransitionConfiguration mIncreaseConfig = new TransitionConfiguration(); - - final TransitionConfiguration mDecreaseConfig = new TransitionConfiguration(); - } - - @VisibleForTesting - static class TransitionConfiguration { - long mDebounceTimeMillis; - - long mTransitionTimeMillis; - } } diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java index 06d3c5b87520..48d671d356f7 100644 --- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java +++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java @@ -18,6 +18,8 @@ package com.android.server.display.config; import android.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -52,7 +54,8 @@ public class HdrBrightnessData { */ public final long mBrightnessDecreaseDurationMillis; - private HdrBrightnessData(Map maxBrightnessLimits, + @VisibleForTesting + public HdrBrightnessData(Map maxBrightnessLimits, long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis, long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) { mMaxBrightnessLimits = maxBrightnessLimits; diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index aebd8a08ee3f..d23c4fe91c84 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -39,6 +39,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_NBM_CONTROLLER, Flags::enableNbmController); + private final FlagState mHdrClamperFlagState = new FlagState( + Flags.FLAG_ENABLE_HDR_CLAMPER, + Flags::enableHdrClamper); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -49,6 +53,10 @@ public class DisplayManagerFlags { return mNbmControllerFlagState.isEnabled(); } + public boolean isHdrClamperEnabled() { + return mHdrClamperFlagState.isEnabled(); + } + private static class FlagState { private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 12306b039225..ce64bd389839 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -14,6 +14,14 @@ flag { name: "enable_nbm_controller" namespace: "display_manager" description: "Feature flag for Normal Brightness Mode Controller" - bug: "277877297" + bug: "299527549" + is_fixed_read_only: true +} + +flag { + name: "enable_hdr_clamper" + namespace: "display_manager" + description: "Feature flag for HDR Clamper" + bug: "295100043" is_fixed_read_only: true } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index d6b2c8732e50..85406b5d12dd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -1256,9 +1256,35 @@ public final class DisplayPowerController2Test { } @Test - public void testRampRateForHdrContent() { + public void testRampRateForHdrContent_HdrClamperOff() { + float hdrBrightness = 0.8f; float clampedBrightness = 0.6f; - float transitionRate = 35.5f; + float transitionRate = 1.5f; + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f); + when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f); + when(mHolder.hbmController.getHighBrightnessMode()).thenReturn( + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR); + when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness); + when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness); + when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator, atLeastOnce()).animateTo(eq(hdrBrightness), anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + } + + @Test + public void testRampRateForHdrContent_HdrClamperOn() { + float clampedBrightness = 0.6f; + float transitionRate = 1.5f; + DisplayManagerFlags flags = mock(DisplayManagerFlags.class); + when(flags.isHdrClamperEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); DisplayPowerRequest dpr = new DisplayPowerRequest(); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); @@ -1361,6 +1387,12 @@ public final class DisplayPowerController2Test { private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId, boolean isEnabled) { + return createDisplayPowerController(displayId, uniqueId, isEnabled, + mock(DisplayManagerFlags.class)); + } + + private DisplayPowerControllerHolder createDisplayPowerController(int displayId, + String uniqueId, boolean isEnabled, DisplayManagerFlags flags) { final DisplayPowerState displayPowerState = mock(DisplayPowerState.class); final DualRampAnimator animator = mock(DualRampAnimator.class); final AutomaticBrightnessController automaticBrightnessController = @@ -1373,7 +1405,6 @@ public final class DisplayPowerController2Test { mock(ScreenOffBrightnessSensorController.class); final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class); final HdrClamper hdrClamper = mock(HdrClamper.class); - final DisplayManagerFlags flags = mock(DisplayManagerFlags.class); when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java index 0ebe46ac0c88..37d966d044c5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java @@ -24,6 +24,7 @@ import android.os.PowerManager; import androidx.test.filters.SmallTest; +import com.android.server.display.config.HdrBrightnessData; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; @@ -34,6 +35,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Map; + @SmallTest public class HdrClamperTest { @@ -108,10 +111,13 @@ public class HdrClamperTest { } private void configureClamper() { - mHdrClamper.getConfiguration().mMaxBrightnessLimits.put(500f, 0.6f); - mHdrClamper.getConfiguration().mIncreaseConfig.mDebounceTimeMillis = 1000; - mHdrClamper.getConfiguration().mIncreaseConfig.mTransitionTimeMillis = 1500; - mHdrClamper.getConfiguration().mDecreaseConfig.mDebounceTimeMillis = 2000; - mHdrClamper.getConfiguration().mDecreaseConfig.mTransitionTimeMillis = 2500; + HdrBrightnessData data = new HdrBrightnessData( + Map.of(500f, 0.6f), + /* brightnessIncreaseDebounceMillis= */ 1000, + /* brightnessIncreaseDurationMillis= */ 1500, + /* brightnessDecreaseDebounceMillis= */ 2000, + /* brightnessDecreaseDurationMillis= */2500 + ); + mHdrClamper.resetHdrConfig(data); } } -- GitLab From 2aaeb2542b71bee6b5cceeeb1cf293a152cca43a Mon Sep 17 00:00:00 2001 From: Beverly Date: Thu, 21 Sep 2023 19:41:57 +0000 Subject: [PATCH 80/95] Manually control the animation LS => bouncer when dragging Set the animator to null instead of using the default animator. The animation wasn't being properly set back to the lockscreen when the bouncer dragging was quickly abandoned since the default animator was being used instead of the manual animation. Test: manual Test: atest KeyguardTransitionScenariosTest Fixes: 299172035 Change-Id: Ic66eb13e7ac0101031622104500d2ebeb85b4032 --- .../FromLockscreenTransitionInteractor.kt | 6 +- .../KeyguardTransitionScenariosTest.kt | 60 ++++++++++++++++++- .../FakeKeyguardTransitionRepository.kt | 2 +- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 9c6a1b1b6cc0..8f3943133eb1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -265,7 +265,11 @@ constructor( !isKeyguardUnlocked && statusBarState == KEYGUARD ) { - transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + transitionId = + startTransitionTo( + toState = KeyguardState.PRIMARY_BOUNCER, + animator = null, // transition will be manually controlled + ) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index d4576053f9c7..f2636c543844 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -39,11 +40,12 @@ import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.domain.model.ShadeModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle @@ -56,6 +58,7 @@ import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.spy @@ -66,6 +69,7 @@ import org.mockito.MockitoAnnotations * Class for testing user journeys through the interactors. They will all be activated during setup, * to ensure the expected transitions are still triggered. */ +@ExperimentalCoroutinesApi @SmallTest @RunWith(JUnit4::class) class KeyguardTransitionScenariosTest : SysuiTestCase() { @@ -74,7 +78,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var bouncerRepository: FakeKeyguardBouncerRepository private lateinit var commandQueue: FakeCommandQueue - private lateinit var shadeRepository: ShadeRepository + private lateinit var shadeRepository: FakeShadeRepository private lateinit var transitionRepository: FakeKeyguardTransitionRepository private lateinit var transitionInteractor: KeyguardTransitionInteractor private lateinit var featureFlags: FakeFeatureFlags @@ -1213,6 +1217,58 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun lockscreenToPrimaryBouncerDragging() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransition(KeyguardState.AOD, KeyguardState.LOCKSCREEN) + runCurrent() + + // GIVEN the keyguard is showing locked + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + keyguardRepository.setKeyguardUnlocked(false) + runCurrent() + shadeRepository.setShadeModel( + ShadeModel( + expansionAmount = .9f, + isUserDragging = true, + ) + ) + runCurrent() + + // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur + val info = + withArgCaptor { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNull() // dragging should be manually animated + + // WHEN the user stops dragging and shade is back to expanded + clearInvocations(transitionRepository) + runTransition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) + shadeRepository.setShadeModel( + ShadeModel( + expansionAmount = 1f, + isUserDragging = false, + ) + ) + runCurrent() + + // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur + val info2 = + withArgCaptor { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info2.animator).isNotNull() + + coroutineContext.cancelChildren() + } + private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index 16442bb525b6..dd513db03d34 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -38,7 +38,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { } override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? { - return null + return if (info.animator == null) UUID.randomUUID() else null } override fun updateTransition( -- GitLab From e51c36f0570146859e9d7272648a06899756b669 Mon Sep 17 00:00:00 2001 From: Santiago Seifert Date: Fri, 22 Sep 2023 12:35:27 +0000 Subject: [PATCH 81/95] Add mShouldPerformActiveScan to RouteDiscoveryPreference#dump Bug: 205124386 Change-Id: Ia9457568662e2014d0d831667f9ebc0f59069d61 Test: adb shell dumpsys media_router --- media/java/android/media/RouteDiscoveryPreference.java | 1 + 1 file changed, 1 insertion(+) diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index 71d8261b09bf..161fd2fb7f96 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -194,6 +194,7 @@ public final class RouteDiscoveryPreference implements Parcelable { String indent = prefix + " "; + pw.println(indent + "mShouldPerformActiveScan=" + mShouldPerformActiveScan); pw.println(indent + "mPreferredFeatures=" + mPreferredFeatures); pw.println(indent + "mPackageOrder=" + mPackageOrder); pw.println(indent + "mAllowedPackages=" + mAllowedPackages); -- GitLab From f324a0d84fc3e46f3f1c80f9e02a07bde256a4d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Pomini?= Date: Tue, 29 Aug 2023 13:05:21 +0000 Subject: [PATCH 82/95] =?UTF-8?q?WallpaperManager=20clear=20API=C2=A0chang?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal is to align WallpaperManager#clear with WallpaperManager#clearWallpaper. There are two changes: - clear(FLAG_SYSTEM) will no longer clear both wallpapers, it will only clear the FLAG_SYSTEM wallpaper (and migrate it to the lock screen if it is a shared wallpaper) - clear() and clear(which) will always set the default wallpaper defined for the device. They won't set an ImageWallpaper unless it's the default wallpaper. Bug: 295176688 Test: atest WallpaperManagerTest Change-Id: I48a83b13eed9a5035f00405f679baf0163bd217d --- core/java/android/app/WallpaperManager.java | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index c31aa01a6b7c..fce5e4f1fd21 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -2429,11 +2429,8 @@ public class WallpaperManager { } /** - * Reset all wallpaper to the factory default. As opposed to {@link #clear()}, if the device - * is configured to have a live wallpaper by default, apply it. - * - *

This method requires the caller to hold the permission - * {@link android.Manifest.permission#SET_WALLPAPER}. + * Equivalent to {@link #clear()}. + * @see #clear() */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clearWallpaper() { @@ -2767,8 +2764,7 @@ public class WallpaperManager { /** * Remove any currently set system wallpaper, reverting to the system's built-in - * wallpaper. As opposed to {@link #clearWallpaper()}, this method always set a static wallpaper - * with the default image, even if the device is configured to have a live wallpaper by default. + * wallpaper. * On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. * *

This method requires the caller to hold the permission @@ -2779,6 +2775,10 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clear() throws IOException { + if (isLockscreenLiveWallpaperEnabled()) { + clear(FLAG_SYSTEM | FLAG_LOCK); + return; + } setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false); } @@ -2787,10 +2787,15 @@ public class WallpaperManager { * display for each one. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} * is broadcast. *

    - *
  • If {@link #FLAG_SYSTEM} is set in the {@code which} parameter, put the default - * wallpaper on both home and lock screen, removing any user defined wallpaper.
  • *
  • When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper. * The home screen wallpaper will become visible on the lock screen.
  • + * + *
  • When called with {@code which=}{@link #FLAG_SYSTEM}, revert the home screen + * wallpaper to default. The lockscreen wallpaper will be unchanged: if the previous + * wallpaper was shared between home and lock screen, it will become lock screen only.
  • + * + *
  • When called with {@code which=}({@link #FLAG_LOCK} | {@link #FLAG_SYSTEM}), put the + * default wallpaper on both home and lock screen, removing any user defined wallpaper.
  • *
* * @param which A bitwise combination of {@link #FLAG_SYSTEM} or @@ -2799,9 +2804,12 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clear(@SetWallpaperFlags int which) throws IOException { + if (isLockscreenLiveWallpaperEnabled()) { + clearWallpaper(which, mContext.getUserId()); + return; + } if ((which & FLAG_SYSTEM) != 0) { clear(); - if (isLockscreenLiveWallpaperEnabled()) return; } if ((which & FLAG_LOCK) != 0) { clearWallpaper(FLAG_LOCK, mContext.getUserId()); -- GitLab From 4272c4c6461523e98cac4a4ad117ec6a0c187689 Mon Sep 17 00:00:00 2001 From: Shaquille Johnson Date: Thu, 21 Sep 2023 15:50:37 +0100 Subject: [PATCH 83/95] Add aconfig for biometrics flags Bug: 282058146 Test: Treehugger Change-Id: Idd66455b28cc61e53c68f559244ad2d022cf65d3 --- AconfigFlags.bp | 14 ++++++++++++++ .../java/android/hardware/biometrics/flags.aconfig | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 core/java/android/hardware/biometrics/flags.aconfig diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 3abaa69270ef..0ec68d7f94e8 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_srcjars = [ ":android.view.flags-aconfig-java{.generated_srcjars}", ":camera_platform_flags_core_java_lib{.generated_srcjars}", ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", + ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":com.android.hardware.input-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", ":telecom_flags_core_java_lib{.generated_srcjars}", @@ -271,6 +272,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Biometrics +aconfig_declarations { + name: "android.hardware.biometrics.flags-aconfig", + package: "android.hardware.biometrics", + srcs: ["core/java/android/hardware/biometrics/flags.aconfig"], +} + +java_aconfig_library { + name: "android.hardware.biometrics.flags-aconfig-java", + aconfig_declarations: "android.hardware.biometrics.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Graphics java_aconfig_library { name: "hwui_flags_java_lib", diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig new file mode 100644 index 000000000000..66429e5046d8 --- /dev/null +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.hardware.biometrics" + +flag { + name: "add_key_agreement_crypto_object" + namespace: "biometrics" + description: "Feature flag for adding KeyAgreement api to CryptoObject." + bug: "282058146" +} + -- GitLab From aabd499a8719ea19238c048dab2df3f9590fd1d9 Mon Sep 17 00:00:00 2001 From: Hawkwood Glazier Date: Thu, 21 Sep 2023 18:53:25 +0000 Subject: [PATCH 84/95] Attempt to reload providers when selected provider is available but unloaded A more minor race may still be present, but this fix should reduce incidence of the issue and be safer than reorganizing the multithreading in verifyLoadedProviders. A subsequent change will address that more directly. Bug: 300671811 Test: Manually forced and checked race condition Change-Id: I5258dddd50b9c89f28340254c53bc1839351e28e --- .../com/android/systemui/shared/clocks/ClockRegistry.kt | 9 ++++++++- .../android/systemui/shared/plugins/PluginInstance.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index fd4a723352e4..ecc0dbad70d0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -148,7 +148,7 @@ open class ClockRegistry( override fun onPluginAttached( manager: PluginLifecycleManager ): Boolean { - manager.isDebug = true + manager.isDebug = !keepAllLoaded if (keepAllLoaded) { // Always load new plugins if requested @@ -511,6 +511,12 @@ open class ClockRegistry( fun verifyLoadedProviders() { val shouldSchedule = isVerifying.compareAndSet(false, true) if (!shouldSchedule) { + logger.tryLog( + TAG, + LogLevel.VERBOSE, + {}, + { "verifyLoadedProviders: shouldSchedule=false" } + ) return } @@ -670,6 +676,7 @@ open class ClockRegistry( { str1 = clockId }, { "Clock $str1 not loaded; using default" } ) + verifyLoadedProviders() } else { logger.tryLog( TAG, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java index 7719e95b9416..f9f2c63c0469 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java @@ -73,7 +73,7 @@ public class PluginInstance implements PluginLifecycleManager mComponentName = componentName; mPluginFactory = pluginFactory; mPlugin = plugin; - mTag = TAG + mComponentName.toShortString() + mTag = TAG + "[" + mComponentName.getShortClassName() + "]" + '@' + Integer.toHexString(hashCode()); if (mPlugin != null) { -- GitLab From e17d87347d611e52d022bbf246c933a2222a1ed7 Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Wed, 20 Sep 2023 19:50:53 +0000 Subject: [PATCH 85/95] [3/n] Optimize user aspect ratio button heuristic If `updateCompatInfo` is called while the user aspect ratio button is showing, the buton will be removed as we no longer consider the layout to have a button as it has already been shown. Update `getHasUserAspectRatioSettingsButton` so the layout is consider to have a button for the duration the button is visible and only once the button has been shown and timed out, will the layout be considered to not have a button. Test: atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest Bug: 301323072 Bug: 289356588 Change-Id: Ie4be836d52b74e285a501a799094022be24870fe --- .../UserAspectRatioSettingsWindowManager.java | 8 +++- ...rAspectRatioSettingsWindowManagerTest.java | 43 ++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 96470012af03..c2dec623416b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -192,6 +192,12 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract } } + @VisibleForTesting + boolean isShowingButton() { + return (mUserAspectRatioButtonShownChecker.get() + && !isHideDelayReached(mNextButtonHideTimeMs)); + } + private void showUserAspectRatioButton() { if (mLayout == null) { return; @@ -224,7 +230,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract return taskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.topActivityBoundsLetterboxed || taskInfo.isUserFullscreenOverrideEnabled) - && !mUserAspectRatioButtonShownChecker.get(); + && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton()); } private long getDisappearTimeMs() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 2a9b72a0bc49..5a4d6c812c17 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -36,6 +36,7 @@ import android.content.ComponentName; import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; import android.view.DisplayInfo; import android.view.InsetsSource; @@ -66,6 +67,7 @@ import org.mockito.MockitoAnnotations; import java.util.HashSet; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.Supplier; /** * Tests for {@link UserAspectRatioSettingsWindowManager}. @@ -74,6 +76,7 @@ import java.util.function.BiConsumer; * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest */ @RunWith(AndroidTestingRunner.class) +@RunWithLooper @SmallTest public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { @@ -81,6 +84,8 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock + private Supplier mUserAspectRatioButtonShownChecker; + @Mock private BiConsumer mOnUserAspectRatioSettingsButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @@ -106,11 +111,12 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { false, /* topActivityBoundsLetterboxed */ true); mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), - mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0, () -> false, - s -> {}); + mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0, + mUserAspectRatioButtonShownChecker, s -> {}); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); } @Test @@ -293,6 +299,39 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { verify(mLayout).setVisibility(View.VISIBLE); } + @Test + public void testLayoutHasUserAspectRatioSettingsButton() { + clearInvocations(mWindowManager); + spyOn(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + + // User aspect ratio settings button has not yet been shown. + doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout has the user aspect ratio settings button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton); + + // User aspect ratio settings button has been shown and is still visible. + spyOn(mWindowManager); + doReturn(true).when(mWindowManager).isShowingButton(); + doReturn(true).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout still has the user aspect ratio settings button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton); + + // User aspect ratio settings button has been shown and has timed out so is no longer + // visible. + doReturn(false).when(mWindowManager).isShowingButton(); + doReturn(true).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout no longer has the user aspect ratio button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertFalse(mWindowManager.mHasUserAspectRatioSettingsButton); + } + @Test public void testAttachToParentSurface() { final SurfaceControl.Builder b = new SurfaceControl.Builder(); -- GitLab From dd87ba45c6e04bbabb1d3b8a68a7495a8cdd3c5c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 21 Sep 2023 21:58:03 -0700 Subject: [PATCH 86/95] Denoise hermetric surfaceflinger perf tests Remove overheard by disabling features during the test including: - perfetto tracing - region sampling by hiding navbar - transaction tracing Reduce variations between each frame by - forcing max frame rate (avoids any frame misses) - consuming transform hint (avoids gpu comp) - starting simpleperf after test setup to move test activity launch outside of measurement window Test: atest android.surfaceflinger.SurfaceFlingerPerfTest Bug: 298240242 Change-Id: Ida7422c37b9afa323147949c9776c042ca97cd08 --- .../perftests/surfaceflinger/Android.bp | 1 + .../surfaceflinger/AndroidManifest.xml | 4 +- .../perftests/surfaceflinger/AndroidTest.xml | 11 +-- .../android/surfaceflinger/BufferFlinger.java | 22 ++++-- .../SurfaceFlingerPerfTest.java | 68 +++++++++++++++++-- .../SurfaceFlingerTestActivity.java | 15 ++++ 6 files changed, 100 insertions(+), 21 deletions(-) diff --git a/apct-tests/perftests/surfaceflinger/Android.bp b/apct-tests/perftests/surfaceflinger/Android.bp index 0c28bcef469c..21d0d44fdd2f 100644 --- a/apct-tests/perftests/surfaceflinger/Android.bp +++ b/apct-tests/perftests/surfaceflinger/Android.bp @@ -32,6 +32,7 @@ android_test { "apct-perftests-utils", "collector-device-lib", "platform-test-annotations", + "cts-wm-util", ], test_suites: ["device-tests"], data: [":perfetto_artifacts"], diff --git a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml index 26f25863f055..aa55b01cc787 100644 --- a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml +++ b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml @@ -16,9 +16,11 @@ - + + + diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml index 58cf58b89782..6dcd86e156d9 100644 --- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml +++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml @@ -44,17 +44,12 @@