diff options
| author | 2024-10-30 16:01:37 +0000 | |
|---|---|---|
| committer | 2024-10-30 16:01:37 +0000 | |
| commit | 8a49f5df7a9c37fc4d097cc39f89ef1dbf5f8e9e (patch) | |
| tree | 77f49169f48943cfe34d87b4c713bc723151d468 | |
| parent | 049a148a378f228c681a1b675a0f5433288640fa (diff) | |
| parent | 261bbcfae0ee3a491c0ab6f14e3a16985d317a37 (diff) | |
Merge changes from topic "pdcd" into main
* changes:
Create Collection extension function `containsExactly`
Extract privacy dot corner logic into a shared enum
Provide ScreenDecorations Executor via Dagger
DisplayWindowProperties: add layout inflater
14 files changed, 279 insertions, 141 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt index 5a764892584e..f68a1b5a17e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import android.content.testableContext import android.platform.test.annotations.EnableFlags import android.view.Display +import android.view.layoutInflater import android.view.mockWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -49,14 +50,18 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { private val applicationContext = kosmos.testableContext private val applicationWindowManager = kosmos.mockWindowManager + private val applicationLayoutInflater = kosmos.layoutInflater - private val repo = + // Lazy so that @EnableFlags has time to run before this repo is instantiated + private val repo by lazy { DisplayWindowPropertiesRepositoryImpl( kosmos.applicationCoroutineScope, applicationContext, applicationWindowManager, + kosmos.layoutInflater, fakeDisplayRepository, ) + } @Before fun start() { @@ -81,6 +86,7 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { windowType = WINDOW_TYPE_FOO, context = applicationContext, windowManager = applicationWindowManager, + layoutInflater = applicationLayoutInflater, ) ) } @@ -102,6 +108,14 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { } @Test + fun get_nonDefaultDisplayId_returnsNewLayoutInflater() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(displayContext.layoutInflater).isNotSameInstanceAs(applicationLayoutInflater) + } + + @Test fun get_multipleCallsForDefaultDisplay_returnsSameInstance() = testScope.runTest { val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt index 16da3d22f4f7..4795a123617f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt @@ -29,6 +29,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.FakeStatusBarStateController +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -71,15 +75,15 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { private fun createController() = PrivacyDotViewControllerImpl( - executor, - testScope.backgroundScope, - statusBarStateController, - configurationController, - contentInsetsProvider, - animationScheduler = mock<SystemStatusAnimationScheduler>(), - shadeInteractor = null, - ) - .also { it.setUiExecutor(executor) } + executor, + testScope.backgroundScope, + statusBarStateController, + configurationController, + contentInsetsProvider, + animationScheduler = mock<SystemStatusAnimationScheduler>(), + shadeInteractor = null, + uiExecutor = executor, + ) @Test fun topMargin_topLeftView_basedOnSeascapeArea() { @@ -215,7 +219,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_RIGHT) + assertThat(controller.currentViewState.corner).isEqualTo(TopRight) assertThat(controller.currentViewState.designatedCorner).isEqualTo(topRightView) } @@ -225,7 +229,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_RIGHT) + assertThat(controller.currentViewState.corner).isEqualTo(BottomRight) assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomRightView) } @@ -235,7 +239,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_LEFT) + assertThat(controller.currentViewState.corner).isEqualTo(TopLeft) assertThat(controller.currentViewState.designatedCorner).isEqualTo(topLeftView) } @@ -245,7 +249,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_LEFT) + assertThat(controller.currentViewState.corner).isEqualTo(BottomLeft) assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomLeftView) } @@ -256,7 +260,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { enableRtl() val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_LEFT) + assertThat(controller.currentViewState.corner).isEqualTo(TopLeft) assertThat(controller.currentViewState.designatedCorner).isEqualTo(topLeftView) } @@ -267,7 +271,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { enableRtl() val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_RIGHT) + assertThat(controller.currentViewState.corner).isEqualTo(TopRight) assertThat(controller.currentViewState.designatedCorner).isEqualTo(topRightView) } @@ -278,7 +282,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { enableRtl() val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_LEFT) + assertThat(controller.currentViewState.corner).isEqualTo(BottomLeft) assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomLeftView) } @@ -289,7 +293,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { enableRtl() val controller = createAndInitializeController() - assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_RIGHT) + assertThat(controller.currentViewState.corner).isEqualTo(BottomRight) assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomRightView) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt new file mode 100644 index 000000000000..2d57e2f01c86 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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 + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ConvenienceExtensionsKtTest : SysuiTestCase() { + + @Test + fun containsExactly_notDuplicatedElements_allSame_returnsTrue() { + val list = listOf(1, 2, 3) + + assertThat(list.containsExactly(2, 1, 3)).isTrue() + } + + @Test + fun containsExactly_duplicatedElements_allSame_returnsTrue() { + val list = listOf(1, 1, 2, 3, 3) + + assertThat(list.containsExactly(1, 1, 2, 3, 3)).isTrue() + } + + @Test + fun containsExactly_duplicatedElements_sameButNotDuplicated_returnsFalse() { + val list = listOf(1, 1, 2, 3, 3) + + assertThat(list.containsExactly(1, 2, 3)).isFalse() + } + + @Test + fun containsExactly_duplicatedElements_sameButNotSameAmount_returnsFalse() { + val list = listOf(1, 1, 2, 3, 3) + + assertThat(list.containsExactly(1, 2, 2, 3, 3)).isFalse() + } + + @Test + fun eachCountMap_returnsExpectedCount() { + val list = listOf(1, 3, 1, 3, 3, 3, 2) + + assertThat(list.eachCountMap()).isEqualTo(mapOf(1 to 2, 2 to 1, 3 to 4)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 9b5d5b6eadca..46e45aaf8a8a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -92,7 +92,6 @@ import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; @@ -147,7 +146,6 @@ public class ScreenDecorations implements private CameraAvailabilityListener mCameraListener; private final UserTracker mUserTracker; private final PrivacyDotViewController mDotViewController; - private final ThreadFactory mThreadFactory; private final DecorProviderFactory mDotFactory; private final FaceScanningProviderFactory mFaceScanningFactory; private final CameraProtectionLoader mCameraProtectionLoader; @@ -172,7 +170,6 @@ public class ScreenDecorations implements private ViewCaptureAwareWindowManager mWindowManager; private int mRotation; private UserSettingObserver mColorInversionSetting; - @Nullable private DelayableExecutor mExecutor; private Handler mHandler; boolean mPendingConfigChange; @@ -327,27 +324,28 @@ public class ScreenDecorations implements } @Inject - public ScreenDecorations(Context context, + public ScreenDecorations( + Context context, SecureSettings secureSettings, CommandRegistry commandRegistry, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, - ThreadFactory threadFactory, PrivacyDotDecorProviderFactory dotFactory, FaceScanningProviderFactory faceScanningFactory, ScreenDecorationsLogger logger, FacePropertyRepository facePropertyRepository, JavaAdapter javaAdapter, CameraProtectionLoader cameraProtectionLoader, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) { + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, + @ScreenDecorationsThread Handler handler, + @ScreenDecorationsThread DelayableExecutor executor) { mContext = context; mSecureSettings = secureSettings; mCommandRegistry = commandRegistry; mUserTracker = userTracker; mDisplayTracker = displayTracker; mDotViewController = dotViewController; - mThreadFactory = threadFactory; mDotFactory = dotFactory; mFaceScanningFactory = faceScanningFactory; mCameraProtectionLoader = cameraProtectionLoader; @@ -356,6 +354,8 @@ public class ScreenDecorations implements mFacePropertyRepository = facePropertyRepository; mJavaAdapter = javaAdapter; mWindowManager = viewCaptureAwareWindowManager; + mHandler = handler; + mExecutor = executor; } private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { @@ -403,10 +403,7 @@ public class ScreenDecorations implements Log.i(TAG, "ScreenDecorations is disabled"); return; } - mHandler = mThreadFactory.buildHandlerOnNewThread("ScreenDecorations"); - mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler); mExecutor.execute(this::startOnScreenDecorationsThread); - mDotViewController.setUiExecutor(mExecutor); mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME, () -> new ScreenDecorCommand(mScreenDecorCommandCallback)); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt index 6fc50fb1f460..6786a71e7d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt @@ -17,16 +17,23 @@ package com.android.systemui import android.content.Context +import android.os.Handler import com.android.systemui.dagger.SysUISingleton import com.android.systemui.decor.FaceScanningProviderFactory import com.android.systemui.decor.FaceScanningProviderFactoryImpl import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet +import java.util.concurrent.Executor +import javax.inject.Qualifier + +@Qualifier annotation class ScreenDecorationsThread @Module interface ScreenDecorationsModule { @@ -41,6 +48,12 @@ interface ScreenDecorationsModule { @IntoSet fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener + @Binds + @ScreenDecorationsThread + fun screenDecorationsExecutor( + @ScreenDecorationsThread delayableExecutor: DelayableExecutor + ): Executor + companion object { @Provides @SysUISingleton @@ -50,5 +63,22 @@ interface ScreenDecorationsModule { ): FaceScanningProviderFactory { return creator.create(context) } + + @Provides + @SysUISingleton + @ScreenDecorationsThread + fun screenDecorationsHandler(threadFactory: ThreadFactory): Handler { + return threadFactory.buildHandlerOnNewThread("ScreenDecorations") + } + + @Provides + @SysUISingleton + @ScreenDecorationsThread + fun screenDecorationsDelayableExecutor( + @ScreenDecorationsThread handler: Handler, + threadFactory: ThreadFactory, + ): DelayableExecutor { + return threadFactory.buildDelayableExecutorOnHandler(handler) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt index 7253621237f6..80eb9eea6c9b 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt @@ -19,7 +19,9 @@ package com.android.systemui.display.data.repository import android.annotation.SuppressLint import android.content.Context import android.view.Display +import android.view.LayoutInflater import android.view.WindowManager +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -30,9 +32,7 @@ import com.google.common.collect.HashBasedTable import com.google.common.collect.Table import java.io.PrintWriter import javax.inject.Inject -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch /** Provides per display instances of [DisplayWindowProperties]. */ interface DisplayWindowPropertiesRepository { @@ -55,6 +55,7 @@ constructor( @Background private val backgroundApplicationScope: CoroutineScope, private val globalContext: Context, private val globalWindowManager: WindowManager, + private val globalLayoutInflater: LayoutInflater, private val displayRepository: DisplayRepository, ) : DisplayWindowPropertiesRepository, CoreStartable { @@ -93,12 +94,14 @@ constructor( windowType = windowType, context = globalContext, windowManager = globalWindowManager, + layoutInflater = globalLayoutInflater, ) } else { val context = createWindowContext(display, windowType) @SuppressLint("NonInjectedService") // Need to manually get the service val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager - DisplayWindowProperties(displayId, windowType, context, windowManager) + val layoutInflater = LayoutInflater.from(context) + DisplayWindowProperties(displayId, windowType, context, windowManager, layoutInflater) } } diff --git a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt index 6acc296367a9..3f1c0881df58 100644 --- a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt +++ b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt @@ -17,6 +17,7 @@ package com.android.systemui.display.shared.model import android.content.Context +import android.view.LayoutInflater import android.view.WindowManager /** Represents a display specific group of window related properties. */ @@ -40,4 +41,7 @@ data class DisplayWindowProperties( * associated with this instance. */ val windowManager: WindowManager, + + /** The [LayoutInflater] to be used with the associated [Context]. */ + val layoutInflater: LayoutInflater, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt index 8a850b0fb199..c416bf7b4f92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.data import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule -import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule @@ -28,7 +27,6 @@ import dagger.Module includes = [ KeyguardStatusBarRepositoryModule::class, - PrivacyDotViewControllerStoreModule::class, RemoteInputRepositoryModule::class, StatusBarConfigurationControllerModule::class, StatusBarContentInsetsProviderStoreModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt new file mode 100644 index 000000000000..8a6f355701cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.events + +import android.view.Gravity +import android.view.Surface + +/** Represents a corner on the display for the privacy dot. */ +enum class PrivacyDotCorner( + val index: Int, + val gravity: Int, + val innerGravity: Int, + val title: String, +) { + TopLeft( + index = 0, + gravity = Gravity.TOP or Gravity.LEFT, + innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, + title = "TopLeft", + ), + TopRight( + index = 1, + gravity = Gravity.TOP or Gravity.RIGHT, + innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, + title = "TopRight", + ), + BottomRight( + index = 2, + gravity = Gravity.BOTTOM or Gravity.RIGHT, + innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, + title = "BottomRight", + ), + BottomLeft( + index = 3, + gravity = Gravity.BOTTOM or Gravity.LEFT, + innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, + title = "BottomLeft", + ), +} + +fun PrivacyDotCorner.rotatedCorner(@Surface.Rotation rotation: Int): PrivacyDotCorner { + var modded = index - rotation + if (modded < 0) { + modded += 4 + } + return PrivacyDotCorner.entries[modded] +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index 914cc50a4a3a..f7bc23c6eb17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -20,12 +20,13 @@ import android.annotation.UiThread import android.graphics.Point import android.graphics.Rect import android.util.Log -import android.view.Gravity import android.view.View import android.widget.FrameLayout import androidx.core.animation.Animator import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.annotations.GuardedBy +import com.android.systemui.ScreenDecorationsThread import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -36,6 +37,10 @@ import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.ConfigurationController @@ -53,7 +58,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch /** * Understands how to keep the persistent privacy dot in the corner of the screen in @@ -81,10 +85,6 @@ interface PrivacyDotViewController { var showingListener: ShowingListener? - fun setUiExecutor(e: DelayableExecutor) - - fun getUiExecutor(): DelayableExecutor? - @UiThread fun setNewRotation(rot: Int) @UiThread fun hideDotView(dot: View, animate: Boolean) @@ -117,6 +117,7 @@ constructor( @Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider, private val animationScheduler: SystemStatusAnimationScheduler, shadeInteractor: ShadeInteractor?, + @ScreenDecorationsThread val uiExecutor: DelayableExecutor, ) : PrivacyDotViewController { private lateinit var tl: View private lateinit var tr: View @@ -136,9 +137,6 @@ constructor( private val lock = Object() private var cancelRunnable: Runnable? = null - // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread - private var uiExecutor: DelayableExecutor? = null - private val views: Sequence<View> get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) @@ -155,7 +153,7 @@ constructor( private val configurationListener = object : ConfigurationController.ConfigurationListener { override fun onLayoutDirectionChanged(isRtl: Boolean) { - uiExecutor?.execute { + uiExecutor.execute { // If rtl changed, hide all dots until the next state resolves setCornerVisibilities(View.INVISIBLE) @@ -198,14 +196,6 @@ constructor( stateController.removeCallback(statusBarStateListener) } - override fun setUiExecutor(e: DelayableExecutor) { - uiExecutor = e - } - - override fun getUiExecutor(): DelayableExecutor? { - return uiExecutor - } - @UiThread override fun setNewRotation(rot: Int) { dlog("updateRotation: $rot") @@ -222,8 +212,8 @@ constructor( // If we rotated, hide all dotes until the next state resolves setCornerVisibilities(View.INVISIBLE) - val newCorner = selectDesignatedCorner(rot, isRtl) - val index = newCorner.cornerIndex() + val newCornerView = selectDesignatedCorner(rot, isRtl) + val corner = newCornerView.corner() val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot) synchronized(lock) { @@ -231,8 +221,8 @@ constructor( nextViewState.copy( rotation = rot, paddingTop = paddingTop, - designatedCorner = newCorner, - cornerIndex = index, + designatedCorner = newCornerView, + corner = corner, ) } } @@ -284,24 +274,15 @@ constructor( views.forEach { corner -> corner.setPadding(0, paddingTop, 0, 0) - val rotatedCorner = rotatedCorner(cornerForView(corner), rotation) + val rotatedCorner = cornerForView(corner).rotatedCorner(rotation) (corner.layoutParams as FrameLayout.LayoutParams).apply { - gravity = rotatedCorner.toGravity() + gravity = rotatedCorner.gravity } // Set the dot's view gravity to hug the status bar (corner.requireViewById<View>(R.id.privacy_dot).layoutParams as FrameLayout.LayoutParams) - .gravity = rotatedCorner.innerGravity() - } - } - - @UiThread - private fun updateCornerSizes(l: Int, r: Int, rotation: Int) { - views.forEach { corner -> - val rotatedCorner = rotatedCorner(cornerForView(corner), rotation) - val w = widthForCorner(rotatedCorner, l, r) - (corner.layoutParams as FrameLayout.LayoutParams).width = w + .gravity = rotatedCorner.innerGravity } } @@ -419,25 +400,16 @@ constructor( } } - private fun cornerForView(v: View): Int { + private fun cornerForView(v: View): PrivacyDotCorner { return when (v) { - tl -> TOP_LEFT - tr -> TOP_RIGHT - bl -> BOTTOM_LEFT - br -> BOTTOM_RIGHT + tl -> TopLeft + tr -> TopRight + bl -> BottomLeft + br -> BottomRight else -> throw IllegalArgumentException("not a corner view") } } - private fun rotatedCorner(corner: Int, rotation: Int): Int { - var modded = corner - rotation - if (modded < 0) { - modded += 4 - } - - return modded - } - @Rotation private fun activeRotationForCorner(corner: View, rtl: Boolean): Int { // Each corner will only be visible in a single rotation, based on rtl @@ -449,16 +421,6 @@ constructor( } } - private fun widthForCorner(corner: Int, left: Int, right: Int): Int { - return when (corner) { - TOP_LEFT, - BOTTOM_LEFT -> left - TOP_RIGHT, - BOTTOM_RIGHT -> right - else -> throw IllegalArgumentException("Unknown corner") - } - } - override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) { if ( this::tl.isInitialized && @@ -478,9 +440,9 @@ constructor( val rtl = configurationController.isLayoutRtl val currentRotation = RotationUtils.getExactRotation(tl.context) - val dc = selectDesignatedCorner(currentRotation, rtl) + val designatedCornerView = selectDesignatedCorner(currentRotation, rtl) - val index = dc.cornerIndex() + val corner = designatedCornerView.corner() mainExecutor.execute { animationScheduler.addCallback(systemStatusAnimationCallback) } @@ -494,8 +456,8 @@ constructor( nextViewState = nextViewState.copy( viewInitialized = true, - designatedCorner = dc, - cornerIndex = index, + designatedCorner = designatedCornerView, + corner = corner, seascapeRect = left, portraitRect = top, landscapeRect = right, @@ -524,7 +486,7 @@ constructor( dlog("scheduleUpdate: ") cancelRunnable?.run() - cancelRunnable = uiExecutor?.executeDelayed({ processNextViewState() }, 100) + cancelRunnable = uiExecutor.executeDelayed({ processNextViewState() }, 100) } @UiThread @@ -613,11 +575,11 @@ constructor( } } - private fun View?.cornerIndex(): Int { + private fun View?.corner(): PrivacyDotCorner? { if (this != null) { return cornerForView(this) } - return -1 + return null } // Returns [left, top, right, bottom] aka [seascape, none, landscape, upside-down] @@ -666,35 +628,11 @@ private fun vlog(s: String) { } } -const val TOP_LEFT = 0 -const val TOP_RIGHT = 1 -const val BOTTOM_RIGHT = 2 -const val BOTTOM_LEFT = 3 private const val DURATION = 160L private const val TAG = "PrivacyDotViewController" private const val DEBUG = false private const val DEBUG_VERBOSE = false -private fun Int.toGravity(): Int { - return when (this) { - TOP_LEFT -> Gravity.TOP or Gravity.LEFT - TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT - BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT - BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT - else -> throw IllegalArgumentException("Not a corner") - } -} - -private fun Int.innerGravity(): Int { - return when (this) { - TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT - TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT - BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT - BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT - else -> throw IllegalArgumentException("Not a corner") - } -} - data class ViewState( val viewInitialized: Boolean = false, val systemPrivacyEventIsActive: Boolean = false, @@ -707,7 +645,7 @@ data class ViewState( val layoutRtl: Boolean = false, val rotation: Int = 0, val paddingTop: Int = 0, - val cornerIndex: Int = -1, + val corner: PrivacyDotCorner? = null, val designatedCorner: View? = null, val contentDescription: String? = null, ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index 99f25bd00839..e4a75beca9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator import com.android.systemui.statusbar.core.StatusBarSimpleFragment +import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks @@ -46,7 +47,9 @@ import dagger.multibindings.IntoMap import kotlinx.coroutines.CoroutineScope /** Similar in purpose to [StatusBarModule], but scoped only to phones */ -@Module(includes = [PrivacyDotViewControllerModule::class]) +@Module( + includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class] +) interface StatusBarPhoneModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt index f2132248e4f7..70fd5ab767d0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt @@ -25,9 +25,7 @@ import java.io.PrintWriter /** [Sequence] that yields all of the direct children of this [ViewGroup] */ val ViewGroup.children - get() = sequence { - for (i in 0 until childCount) yield(getChildAt(i)) - } + get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) } /** Inclusive version of [Iterable.takeWhile] */ fun <T> Sequence<T>.takeUntil(pred: (T) -> Boolean): Sequence<T> = sequence { @@ -62,3 +60,25 @@ val View.boundsOnScreen: Rect fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> { return lazy { this.get() } } + +/** + * Returns whether this [Collection] contains exactly all [elements]. + * + * Order of elements is not taken into account, but multiplicity is. For example, an element + * duplicated exactly 3 times in the parameter asserts that the element must likewise be duplicated + * exactly 3 times in this [Collection]. + */ +fun <T> Collection<T>.containsExactly(vararg elements: T): Boolean { + return eachCountMap() == elements.asList().eachCountMap() +} + +/** + * Returns a map where keys are the distinct elements of the collection and values are their + * corresponding counts. + * + * This is a convenient extension function for any [Collection] that allows you to easily count the + * occurrences of each element. + */ +fun <T> Collection<T>.eachCountMap(): Map<T, Int> { + return groupingBy { it }.eachCount() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 344d065979f9..0769ada805a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -104,6 +104,7 @@ import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.kotlin.JavaAdapter; @@ -186,16 +187,17 @@ public class ScreenDecorationsTest extends SysuiTestCase { private List<DecorProvider> mMockCutoutList; private final CameraProtectionLoader mCameraProtectionLoader = new CameraProtectionLoaderImpl(mContext); + private Handler mMainHandler; @Before public void setup() { MockitoAnnotations.initMocks(this); - Handler mainHandler = new Handler(TestableLooper.get(this).getLooper()); + mMainHandler = new Handler(TestableLooper.get(this).getLooper()); mSecureSettings = new FakeSettings(); mExecutor = new FakeExecutor(new FakeSystemClock()); mThreadFactory = new FakeThreadFactory(mExecutor); - mThreadFactory.setHandler(mainHandler); + mThreadFactory.setHandler(mMainHandler); mWindowManager = mock(WindowManager.class); WindowMetrics metrics = mContext.getSystemService(WindowManager.class) @@ -214,26 +216,26 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mMockTypedArray.length()).thenReturn(0); mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( R.id.privacy_dot_top_left_container, - DisplayCutout.BOUNDS_POSITION_TOP, - DisplayCutout.BOUNDS_POSITION_LEFT, + BOUNDS_POSITION_TOP, + BOUNDS_POSITION_LEFT, R.layout.privacy_dot_top_left)); mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( R.id.privacy_dot_top_right_container, - DisplayCutout.BOUNDS_POSITION_TOP, - DisplayCutout.BOUNDS_POSITION_RIGHT, + BOUNDS_POSITION_TOP, + BOUNDS_POSITION_RIGHT, R.layout.privacy_dot_top_right)); mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( R.id.privacy_dot_bottom_left_container, - DisplayCutout.BOUNDS_POSITION_BOTTOM, - DisplayCutout.BOUNDS_POSITION_LEFT, + BOUNDS_POSITION_BOTTOM, + BOUNDS_POSITION_LEFT, R.layout.privacy_dot_bottom_left)); mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( R.id.privacy_dot_bottom_right_container, - DisplayCutout.BOUNDS_POSITION_BOTTOM, - DisplayCutout.BOUNDS_POSITION_RIGHT, + BOUNDS_POSITION_BOTTOM, + BOUNDS_POSITION_RIGHT, R.layout.privacy_dot_bottom_right)); // Default no cutout @@ -256,11 +258,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { mLazyViewCapture, false); mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, - mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader, - mViewCaptureAwareWindowManager) { + mViewCaptureAwareWindowManager, mMainHandler, mExecutor) { @Override public void start() { super.start(); @@ -1272,10 +1273,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, - mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, + mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader, - mViewCaptureAwareWindowManager); + mViewCaptureAwareWindowManager, mMainHandler, mExecutor); screenDecorations.start(); when(mContext.getDisplay()).thenReturn(mDisplay); when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt index 9282f275b20b..92dc89747db8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt @@ -31,6 +31,7 @@ class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository windowType = windowType, context = mock(), windowManager = mock(), + layoutInflater = mock(), ) .also { properties.put(displayId, windowType, it) } } |