Quick affordances crash fix for secondary users.

This CL fixes a few issues that are making lock screen customization and
lock screen shortcuts unavailable for secondary users.

The attached bug occurs because we're querying state off the main
thread. The first part of the CL modifies our content provider to move
its querying onto the main thread while blocking the calling thread
(which is a background thread).

After fixing that, several problems emerged. When we query for
affordances, we iterate through each affordance configuration and check
its "picker state". For the camera and flashlight affordances, this was
problematic.

The camera affordance had a complex dependnecy chain that ended up
relying on DevicePolicyManager which was not being set until after the
CoreStartable for CoreSurfacesImpl ran its start() method, which doesn't
happen at all for secondary users. I worked around that issue by
simplifying the use-case in the camera affordance to just directly check
whether the device has camera hardware. If an admin doesn't allow it, I
presume that opening the camera app will fail.

The flashlight affordance triggered a broadcast to be sent for
FLASHLIGHT_CHANGED. The problem is that this is a protected broadcast
designed to prevent non-framework apps from sending it; which also means
that our secondary user process does not have the necessary security
permission to send it, causing the SystemUI process to crash. I removed the broadcast sending. This was not needed by the flashlight affordance or
by the quick setting or by the smart space "turn off flashlight" link.
Also, the unit test for the controller passes.

Fix: 265245824
Test: unit tests still pass, some were updated to pass
Test: manully verified the wallpaper picker loads and shows all
affordance options for both primary and secondary users
Test: manually verified that selecting any of the affordance options in
wallpaper picker produces fully working experiences for the lock screen
shortcuts (tested all on both primary and secondary user)
Test: manually verified that flashlight toggles on and off correctly
from the lock screen shortcut, smart space, and quick settings tile, for
primary and secondary users alike

Change-Id: Ifb2b47e7d521c67378b2c64f6f32f863319a5b5d
This commit is contained in:
Alejandro Nijamkin
2023-01-25 13:24:13 -08:00
parent 87f4c66c0a
commit 2c4c44d195
6 changed files with 26 additions and 12 deletions

View File

@@ -52,6 +52,10 @@ A picker experience may:
* Unselect an already-selected quick affordance from a slot
* Unselect all already-selected quick affordances from a slot
## Testing
* Add a unit test for your implementation of `KeyguardQuickAffordanceConfig`
* Manually verify that your implementation works in multi-user environments from both the main user and a secondary user
## Debugging
To see the current state of the system, you can run `dumpsys`:

View File

@@ -31,10 +31,12 @@ import android.os.Bundle
import android.util.Log
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
class CustomizationProvider :
@@ -42,6 +44,7 @@ class CustomizationProvider :
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
@Inject lateinit var previewManager: KeyguardRemotePreviewManager
@Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
private lateinit var contextAvailableCallback: ContextAvailableCallback
@@ -138,12 +141,14 @@ class CustomizationProvider :
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
return when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
MATCH_CODE_ALL_SLOTS -> querySlots()
MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
MATCH_CODE_ALL_FLAGS -> queryFlags()
else -> null
return runBlocking(mainDispatcher) {
when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
MATCH_CODE_ALL_SLOTS -> querySlots()
MATCH_CODE_ALL_SELECTIONS -> querySelections()
MATCH_CODE_ALL_FLAGS -> queryFlags()
else -> null
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
import android.content.Context
import android.content.pm.PackageManager
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.camera.CameraGestureHelper
@@ -26,7 +27,6 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.StatusBarState
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -37,6 +37,7 @@ class CameraQuickAffordanceConfig
@Inject
constructor(
@Application private val context: Context,
private val packageManager: PackageManager,
private val cameraGestureHelper: Lazy<CameraGestureHelper>,
) : KeyguardQuickAffordanceConfig {
@@ -79,6 +80,6 @@ constructor(
}
private fun isLaunchable(): Boolean {
return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
}

View File

@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.WorkerThread;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
@@ -255,7 +254,6 @@ public class FlashlightControllerImpl implements FlashlightController {
setTorchMode(enabled);
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}

View File

@@ -62,6 +62,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -184,6 +185,7 @@ class CustomizationProviderTest : SysuiTestCase() {
mainDispatcher = testDispatcher,
backgroundHandler = backgroundHandler,
)
underTest.mainDispatcher = UnconfinedTestDispatcher()
underTest.attachInfoForTesting(
context,

View File

@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
import android.content.Context
import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
@@ -32,7 +33,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -43,16 +43,19 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
@Mock private lateinit var packageManager: PackageManager
private lateinit var underTest: CameraQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
setLaunchable(true)
underTest =
CameraQuickAffordanceConfig(
context,
packageManager,
) {
cameraGestureHelper
}
@@ -86,6 +89,7 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
}
private fun setLaunchable(isLaunchable: Boolean) {
whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
.thenReturn(isLaunchable)
}
}