summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Göllner <chrisgollner@google.com> 2024-10-30 16:01:37 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-10-30 16:01:37 +0000
commit8a49f5df7a9c37fc4d097cc39f89ef1dbf5f8e9e (patch)
tree77f49169f48943cfe34d87b4c713bc723151d468
parent049a148a378f228c681a1b675a0f5433288640fa (diff)
parent261bbcfae0ee3a491c0ab6f14e3a16985d317a37 (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
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt)16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt1
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) }
}