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:
@@ -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`:
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user