diff options
18 files changed, 572 insertions, 229 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index d28a9f3cf8ff..efe938f0a274 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -612,12 +612,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb new DisplayInsetsController.OnInsetsChangedListener() { @Override public void insetsChanged(InsetsState insetsState) { + DisplayLayout pendingLayout = + mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()); + if (mIsInFixedRotation + || pendingLayout.rotation() + != mPipBoundsState.getDisplayLayout().rotation()) { + // bail out if there is a pending rotation or fixed rotation change + return; + } int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; onDisplayChanged( mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()), false /* saveRestoreSnapFraction */); int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; if (!mEnablePipKeepClearAlgorithm) { + // offset PiP to adjust for bottom inset change int pipTop = mPipBoundsState.getBounds().top; int diff = newMaxMovementBound - oldMaxMovementBound; if (diff < 0 && pipTop > newMaxMovementBound) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index af05078b4f3a..73bb5eb46f33 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -34,6 +34,8 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.UserManager; +import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import java.util.Arrays; @@ -96,6 +98,22 @@ public class InstallStart extends Activity { mAbortInstall = true; } } + + final String installerPackageNameFromIntent = getIntent().getStringExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME); + if (installerPackageNameFromIntent != null) { + final String callingPkgName = getLaunchedFromPackage(); + if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName) + && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES, + callingPkgName) != PackageManager.PERMISSION_GRANTED) { + Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent + + " is invalid. Remove it."); + EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(), + "Invalid EXTRA_INSTALLER_PACKAGE_NAME"); + getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); + } + } + if (mAbortInstall) { setResult(RESULT_CANCELED); finish(); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 5963ca3e719b..d1ac7d00c1cf 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1125,7 +1125,7 @@ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string> <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited --> - <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string> + <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string> <!-- [CHAR_LIMIT=80] Label for battery charging future pause --> <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string> diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml index 579824a2a734..57e5f8a49535 100644 --- a/packages/SystemUI/res-keyguard/values-nl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml @@ -74,7 +74,7 @@ <string name="kg_password_pin_failed" msgid="5136259126330604009">"Bewerking met pincode voor simkaart is mislukt."</string> <string name="kg_password_puk_failed" msgid="6778867411556937118">"Bewerking met pukcode voor simkaart is mislukt."</string> <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string> - <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string> + <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string> <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index a129fb650ba6..da485a99c29b 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -53,7 +53,7 @@ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string> <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. --> - <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string> + <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string> <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. --> <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string> diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt new file mode 100644 index 000000000000..3d10ab906f2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.controls + +import kotlinx.coroutines.flow.StateFlow + +/** Repository for Device controls related settings. */ +interface ControlsSettingsRepository { + /** Whether device controls activity can be shown above lockscreen for this user. */ + val canShowControlsInLockscreen: StateFlow<Boolean> + + /** Whether trivial controls can be actioned from the lockscreen for this user. */ + val allowActionOnTrivialControlsInLockscreen: StateFlow<Boolean> +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt new file mode 100644 index 000000000000..9dc422a09674 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.controls + +import android.provider.Settings +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.SettingObserver +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn + +/** + * This implementation uses an `@Application` [CoroutineScope] to provide hot flows for the values + * of the tracked settings. + */ +@SysUISingleton +class ControlsSettingsRepositoryImpl +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val secureSettings: SecureSettings +) : ControlsSettingsRepository { + + override val canShowControlsInLockscreen = + makeFlowForSetting(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS) + + override val allowActionOnTrivialControlsInLockscreen = + makeFlowForSetting(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS) + + @OptIn(ExperimentalCoroutinesApi::class) + private fun makeFlowForSetting(setting: String): StateFlow<Boolean> { + return userRepository.selectedUserInfo + .distinctUntilChanged() + .flatMapLatest { userInfo -> + conflatedCallbackFlow { + val observer = + object : SettingObserver(secureSettings, null, setting, userInfo.id) { + override fun handleValueChanged( + value: Int, + observedChange: Boolean + ) { + trySend(value == 1) + } + } + observer.isListening = true + trySend(observer.value == 1) + awaitClose { observer.isListening = false } + } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + } + .stateIn( + scope, + started = SharingStarted.Eagerly, + // When the observer starts listening, the flow will emit the current value + // so the initialValue here is irrelevant. + initialValue = false, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt index 9e4a364562e5..77d0496e43db 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt @@ -16,13 +16,10 @@ package com.android.systemui.controls.dagger -import android.content.ContentResolver import android.content.Context -import android.database.ContentObserver -import android.os.UserHandle -import android.provider.Settings import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT +import com.android.systemui.controls.ControlsSettingsRepository import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsTileResourceConfiguration import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl @@ -31,12 +28,10 @@ import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import dagger.Lazy +import kotlinx.coroutines.flow.StateFlow import java.util.Optional import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow /** * Pseudo-component to inject into classes outside `com.android.systemui.controls`. @@ -46,47 +41,26 @@ import kotlinx.coroutines.flow.asStateFlow */ @SysUISingleton class ControlsComponent @Inject constructor( - @ControlsFeatureEnabled private val featureEnabled: Boolean, - private val context: Context, - private val lazyControlsController: Lazy<ControlsController>, - private val lazyControlsUiController: Lazy<ControlsUiController>, - private val lazyControlsListingController: Lazy<ControlsListingController>, - private val lockPatternUtils: LockPatternUtils, - private val keyguardStateController: KeyguardStateController, - private val userTracker: UserTracker, - private val secureSettings: SecureSettings, - private val optionalControlsTileResourceConfiguration: - Optional<ControlsTileResourceConfiguration> + @ControlsFeatureEnabled private val featureEnabled: Boolean, + private val context: Context, + private val lazyControlsController: Lazy<ControlsController>, + private val lazyControlsUiController: Lazy<ControlsUiController>, + private val lazyControlsListingController: Lazy<ControlsListingController>, + private val lockPatternUtils: LockPatternUtils, + private val keyguardStateController: KeyguardStateController, + private val userTracker: UserTracker, + controlsSettingsRepository: ControlsSettingsRepository, + optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> ) { - private val contentResolver: ContentResolver - get() = context.contentResolver - private val _canShowWhileLockedSetting = MutableStateFlow(false) - val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow() + val canShowWhileLockedSetting: StateFlow<Boolean> = + controlsSettingsRepository.canShowControlsInLockscreen private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration = optionalControlsTileResourceConfiguration.orElse( ControlsTileResourceConfigurationImpl() ) - val showWhileLockedObserver = object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - updateShowWhileLocked() - } - } - - init { - if (featureEnabled) { - secureSettings.registerContentObserverForUser( - Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), - false, /* notifyForDescendants */ - showWhileLockedObserver, - UserHandle.USER_ALL - ) - updateShowWhileLocked() - } - } - fun getControlsController(): Optional<ControlsController> { return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty() } @@ -127,11 +101,6 @@ class ControlsComponent @Inject constructor( return Visibility.AVAILABLE } - private fun updateShowWhileLocked() { - _canShowWhileLockedSetting.value = secureSettings.getIntForUser( - Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 - } - enum class Visibility { AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE } diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index 6f58abdeed56..9ae605e30a83 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -20,6 +20,8 @@ import android.app.Activity import android.content.pm.PackageManager import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsMetricsLoggerImpl +import com.android.systemui.controls.ControlsSettingsRepository +import com.android.systemui.controls.ControlsSettingsRepositoryImpl import com.android.systemui.controls.controller.ControlsBindingController import com.android.systemui.controls.controller.ControlsBindingControllerImpl import com.android.systemui.controls.controller.ControlsController @@ -83,6 +85,11 @@ abstract class ControlsModule { abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController @Binds + abstract fun provideSettingsManager( + manager: ControlsSettingsRepositoryImpl + ): ControlsSettingsRepository + + @Binds abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger @Binds diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index b8a00133c728..8472ca0731d7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -24,9 +24,6 @@ import android.app.PendingIntent import android.content.Context import android.content.pm.PackageManager import android.content.pm.ResolveInfo -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler import android.os.UserHandle import android.os.VibrationEffect import android.provider.Settings.Secure @@ -40,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger +import com.android.systemui.controls.ControlsSettingsRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -68,17 +66,17 @@ class ControlActionCoordinatorImpl @Inject constructor( private val vibrator: VibratorHelper, private val secureSettings: SecureSettings, private val userContextProvider: UserContextProvider, - @Main mainHandler: Handler + private val controlsSettingsRepository: ControlsSettingsRepository, ) : ControlActionCoordinator { private var dialog: Dialog? = null private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() private val isLocked: Boolean get() = !keyguardStateController.isUnlocked() - private var mAllowTrivialControls: Boolean = secureSettings.getIntForUser( - Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 - private var mShowDeviceControlsInLockscreen: Boolean = secureSettings.getIntForUser( - Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 + private val allowTrivialControls: Boolean + get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value + private val showDeviceControlsInLockscreen: Boolean + get() = controlsSettingsRepository.canShowControlsInLockscreen.value override lateinit var activityContext: Context companion object { @@ -86,38 +84,6 @@ class ControlActionCoordinatorImpl @Inject constructor( private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2 } - init { - val lockScreenShowControlsUri = - secureSettings.getUriFor(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS) - val showControlsUri = - secureSettings.getUriFor(Secure.LOCKSCREEN_SHOW_CONTROLS) - val controlsContentObserver = object : ContentObserver(mainHandler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - when (uri) { - lockScreenShowControlsUri -> { - mAllowTrivialControls = secureSettings.getIntForUser( - Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - 0, UserHandle.USER_CURRENT) != 0 - } - showControlsUri -> { - mShowDeviceControlsInLockscreen = secureSettings - .getIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS, - 0, UserHandle.USER_CURRENT) != 0 - } - } - } - } - secureSettings.registerContentObserverForUser( - lockScreenShowControlsUri, - false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL - ) - secureSettings.registerContentObserverForUser( - showControlsUri, - false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL - ) - } - override fun closeDialogs() { dialog?.dismiss() dialog = null @@ -224,7 +190,7 @@ class ControlActionCoordinatorImpl @Inject constructor( @AnyThread @VisibleForTesting fun bouncerOrRun(action: Action) { - val authRequired = action.authIsRequired || !mAllowTrivialControls + val authRequired = action.authIsRequired || !allowTrivialControls if (keyguardStateController.isShowing() && authRequired) { if (isLocked) { @@ -282,7 +248,7 @@ class ControlActionCoordinatorImpl @Inject constructor( PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0) if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG || - (mShowDeviceControlsInLockscreen && mAllowTrivialControls)) { + (showDeviceControlsInLockscreen && allowTrivialControls)) { return } val builder = AlertDialog @@ -304,7 +270,7 @@ class ControlActionCoordinatorImpl @Inject constructor( true } - if (mShowDeviceControlsInLockscreen) { + if (showDeviceControlsInLockscreen) { dialog = builder .setTitle(R.string.controls_settings_trivial_controls_dialog_title) .setMessage(R.string.controls_settings_trivial_controls_dialog_message) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index 49527d32d229..62fe80a82908 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -21,50 +21,52 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 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.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.statusbar.policy.FlashlightController +import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import javax.inject.Inject @SysUISingleton -class FlashlightQuickAffordanceConfig @Inject constructor( - @Application private val context: Context, - private val flashlightController: FlashlightController, +class FlashlightQuickAffordanceConfig +@Inject +constructor( + @Application private val context: Context, + private val flashlightController: FlashlightController, ) : KeyguardQuickAffordanceConfig { private sealed class FlashlightState { abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState - object On: FlashlightState() { + object On : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( - R.drawable.ic_flashlight_on, + R.drawable.qs_flashlight_icon_on, ContentDescription.Resource(R.string.quick_settings_flashlight_label) ), ActivationState.Active ) } - object OffAvailable: FlashlightState() { + object OffAvailable : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( - R.drawable.ic_flashlight_off, + R.drawable.qs_flashlight_icon_off, ContentDescription.Resource(R.string.quick_settings_flashlight_label) ), ActivationState.Inactive ) } - object Unavailable: FlashlightState() { + object Unavailable : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Hidden } @@ -77,57 +79,57 @@ class FlashlightQuickAffordanceConfig @Inject constructor( get() = context.getString(R.string.quick_settings_flashlight_label) override val pickerIconResourceId: Int - get() = if (flashlightController.isEnabled) { - R.drawable.ic_flashlight_on - } else { - R.drawable.ic_flashlight_off - } + get() = R.drawable.ic_flashlight_off override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = - conflatedCallbackFlow { - val flashlightCallback = object : FlashlightController.FlashlightListener { - override fun onFlashlightChanged(enabled: Boolean) { - trySendWithFailureLogging( - if (enabled) { - FlashlightState.On.toLockScreenState() - } else { - FlashlightState.OffAvailable.toLockScreenState() - }, - TAG - ) - } - - override fun onFlashlightError() { - trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG) - } - - override fun onFlashlightAvailabilityChanged(available: Boolean) { - trySendWithFailureLogging( - if (!available) { - FlashlightState.Unavailable.toLockScreenState() - } else { - if (flashlightController.isEnabled) { - FlashlightState.On.toLockScreenState() - } else { - FlashlightState.OffAvailable.toLockScreenState() - } - }, - TAG - ) - } + conflatedCallbackFlow { + val flashlightCallback = + object : FlashlightController.FlashlightListener { + override fun onFlashlightChanged(enabled: Boolean) { + trySendWithFailureLogging( + if (enabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + }, + TAG + ) + } + + override fun onFlashlightError() { + trySendWithFailureLogging( + FlashlightState.OffAvailable.toLockScreenState(), + TAG + ) + } + + override fun onFlashlightAvailabilityChanged(available: Boolean) { + trySendWithFailureLogging( + if (!available) { + FlashlightState.Unavailable.toLockScreenState() + } else { + if (flashlightController.isEnabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + } + }, + TAG + ) + } + } + + flashlightController.addCallback(flashlightCallback) + + awaitClose { flashlightController.removeCallback(flashlightCallback) } } - flashlightController.addCallback(flashlightCallback) - - awaitClose { - flashlightController.removeCallback(flashlightCallback) - } - } - - override fun onTriggered(expandable: Expandable?): - KeyguardQuickAffordanceConfig.OnTriggeredResult { - flashlightController - .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled) + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + flashlightController.setFlashlight( + flashlightController.isAvailable && !flashlightController.isEnabled + ) return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } @@ -141,4 +143,4 @@ class FlashlightQuickAffordanceConfig @Inject constructor( companion object { private const val TAG = "FlashlightQuickAffordanceConfig" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 3276b6dd9748..cbe512ff83ba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.binder +import android.graphics.drawable.Animatable2 import android.util.Size import android.util.TypedValue import android.view.View @@ -27,12 +28,11 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.LockIconViewController import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.animation.Interpolators +import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel @@ -73,7 +73,8 @@ object KeyguardBottomAreaViewBinder { fun onConfigurationChanged() /** - * Returns whether the keyguard bottom area should be constrained to the top of the lock icon + * Returns whether the keyguard bottom area should be constrained to the top of the lock + * icon */ fun shouldConstrainToTopOfLockIcon(): Boolean } @@ -217,7 +218,7 @@ object KeyguardBottomAreaViewBinder { } override fun shouldConstrainToTopOfLockIcon(): Boolean = - viewModel.shouldConstrainToTopOfLockIcon() + viewModel.shouldConstrainToTopOfLockIcon() } } @@ -248,6 +249,27 @@ object KeyguardBottomAreaViewBinder { IconViewBinder.bind(viewModel.icon, view) + (view.drawable as? Animatable2)?.let { animatable -> + (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId -> + // Always start the animation (we do call stop() below, if we need to skip it). + animatable.start() + + if (view.tag != iconResourceId) { + // Here when we haven't run the animation on a previous update. + // + // Save the resource ID for next time, so we know not to re-animate the same + // animation again. + view.tag = iconResourceId + } else { + // Here when we've already done this animation on a previous update and want to + // skip directly to the final frame of the animation to avoid running it. + // + // By calling stop after start, we go to the final frame of the animation. + animatable.stop() + } + } + } + view.isActivated = viewModel.isActivated view.drawable.setTint( Utils.getColorAttrDefaultColor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt new file mode 100644 index 000000000000..4b88b44c3f03 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.controls + +import android.content.pm.UserInfo +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class ControlsSettingsRepositoryImplTest : SysuiTestCase() { + + companion object { + private const val LOCKSCREEN_SHOW = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS + private const val LOCKSCREEN_ACTION = Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS + + private fun createUser(id: Int): UserInfo { + return UserInfo(id, "user_$id", 0) + } + + private val ALL_USERS = (0..1).map { it to createUser(it) }.toMap() + } + + private lateinit var underTest: ControlsSettingsRepository + + private lateinit var testScope: TestScope + private lateinit var secureSettings: FakeSettings + private lateinit var userRepository: FakeUserRepository + + @Before + fun setUp() { + secureSettings = FakeSettings() + userRepository = FakeUserRepository() + userRepository.setUserInfos(ALL_USERS.values.toList()) + + val coroutineDispatcher = UnconfinedTestDispatcher() + testScope = TestScope(coroutineDispatcher) + + underTest = + ControlsSettingsRepositoryImpl( + scope = testScope.backgroundScope, + backgroundDispatcher = coroutineDispatcher, + userRepository = userRepository, + secureSettings = secureSettings, + ) + } + + @Test + fun showInLockScreen() = + testScope.runTest { + setUser(0) + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.canShowControlsInLockscreen.toList(values) + } + assertThat(values.last()).isFalse() + + secureSettings.putBool(LOCKSCREEN_SHOW, true) + assertThat(values.last()).isTrue() + + secureSettings.putBool(LOCKSCREEN_SHOW, false) + assertThat(values.last()).isFalse() + + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1) + assertThat(values.last()).isFalse() + + setUser(1) + assertThat(values.last()).isTrue() + + job.cancel() + } + + @Test + fun showInLockScreen_changesInOtherUsersAreNotQueued() = + testScope.runTest { + setUser(0) + + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.canShowControlsInLockscreen.toList(values) + } + + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1) + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, false, 1) + + setUser(1) + assertThat(values.last()).isFalse() + assertThat(values).containsNoneIn(listOf(true)) + + job.cancel() + } + + @Test + fun actionInLockScreen() = + testScope.runTest { + setUser(0) + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.allowActionOnTrivialControlsInLockscreen.toList(values) + } + assertThat(values.last()).isFalse() + + secureSettings.putBool(LOCKSCREEN_ACTION, true) + assertThat(values.last()).isTrue() + + secureSettings.putBool(LOCKSCREEN_ACTION, false) + assertThat(values.last()).isFalse() + + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1) + assertThat(values.last()).isFalse() + + setUser(1) + assertThat(values.last()).isTrue() + + job.cancel() + } + + @Test + fun actionInLockScreen_changesInOtherUsersAreNotQueued() = + testScope.runTest { + setUser(0) + + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.allowActionOnTrivialControlsInLockscreen.toList(values) + } + + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1) + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, false, 1) + + setUser(1) + assertThat(values.last()).isFalse() + assertThat(values).containsNoneIn(listOf(true)) + + job.cancel() + } + + @Test + fun valueIsUpdatedWhenNotSubscribed() = + testScope.runTest { + setUser(0) + assertThat(underTest.canShowControlsInLockscreen.value).isFalse() + + secureSettings.putBool(LOCKSCREEN_SHOW, true) + + assertThat(underTest.canShowControlsInLockscreen.value).isTrue() + } + + private suspend fun setUser(id: Int) { + secureSettings.userId = id + userRepository.setSelectedUserInfo(ALL_USERS[id]!!) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt new file mode 100644 index 000000000000..8a1bed20e700 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.controls + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeControlsSettingsRepository : ControlsSettingsRepository { + private val _canShowControlsInLockscreen = MutableStateFlow(false) + override val canShowControlsInLockscreen = _canShowControlsInLockscreen.asStateFlow() + private val _allowActionOnTrivialControlsInLockscreen = MutableStateFlow(false) + override val allowActionOnTrivialControlsInLockscreen = + _allowActionOnTrivialControlsInLockscreen.asStateFlow() + + fun setCanShowControlsInLockscreen(value: Boolean) { + _canShowControlsInLockscreen.value = value + } + + fun setAllowActionOnTrivialControlsInLockscreen(value: Boolean) { + _allowActionOnTrivialControlsInLockscreen.value = value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt index 4ed5649c9c50..1d00d6b05568 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -18,30 +18,24 @@ package com.android.systemui.controls.ui import android.content.Context import android.content.SharedPreferences -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.UserHandle -import android.provider.Settings import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger +import com.android.systemui.controls.FakeControlsSettingsRepository import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.SecureSettings import com.android.wm.shell.TaskViewFactory import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` @@ -79,8 +73,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { @Mock private lateinit var secureSettings: SecureSettings @Mock - private lateinit var mainHandler: Handler - @Mock private lateinit var userContextProvider: UserContextProvider companion object { @@ -91,17 +83,15 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { private lateinit var coordinator: ControlActionCoordinatorImpl private lateinit var action: ControlActionCoordinatorImpl.Action + private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository @Before fun setUp() { MockitoAnnotations.initMocks(this) - `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)) - .thenReturn(Settings.Secure - .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)) - `when`(secureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - 0, UserHandle.USER_CURRENT)) - .thenReturn(1) + controlsSettingsRepository = FakeControlsSettingsRepository() + controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) + controlsSettingsRepository.setCanShowControlsInLockscreen(true) coordinator = spy(ControlActionCoordinatorImpl( mContext, @@ -115,7 +105,7 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { vibratorHelper, secureSettings, userContextProvider, - mainHandler + controlsSettingsRepository )) val userContext = mock(Context::class.java) @@ -128,9 +118,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)) .thenReturn(2) - verify(secureSettings).registerContentObserverForUser(any(Uri::class.java), - anyBoolean(), any(ContentObserver::class.java), anyInt()) - `when`(cvh.cws.ci.controlId).thenReturn(ID) `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true) action = spy(coordinator.Action(ID, {}, false, true)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt index 77f451f61c3c..48fc46b7e730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt @@ -17,19 +17,18 @@ package com.android.systemui.controls.dagger import android.testing.AndroidTestingRunner -import android.provider.Settings import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.FakeControlsSettingsRepository import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsTileResourceConfiguration import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import dagger.Lazy import java.util.Optional import org.junit.Assert.assertEquals @@ -63,13 +62,13 @@ class ControlsComponentTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock - private lateinit var secureSettings: SecureSettings - @Mock private lateinit var optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> @Mock private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration + private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository + companion object { fun <T> eq(value: T): T = Mockito.eq(value) ?: value } @@ -78,6 +77,8 @@ class ControlsComponentTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + controlsSettingsRepository = FakeControlsSettingsRepository() + `when`(userTracker.userHandle.identifier).thenReturn(0) `when`(optionalControlsTileResourceConfiguration.orElse(any())) .thenReturn(controlsTileResourceConfiguration) @@ -125,8 +126,7 @@ class ControlsComponentTest : SysuiTestCase() { `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) - `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt())) - .thenReturn(0) + controlsSettingsRepository.setCanShowControlsInLockscreen(false) val component = setupComponent(true) assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility()) @@ -137,9 +137,7 @@ class ControlsComponentTest : SysuiTestCase() { `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) - `when`(secureSettings.getIntForUser(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), - anyInt(), anyInt())) - .thenReturn(1) + controlsSettingsRepository.setCanShowControlsInLockscreen(true) val component = setupComponent(true) assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility()) @@ -147,8 +145,7 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() { - `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt())) - .thenReturn(0) + controlsSettingsRepository.setCanShowControlsInLockscreen(false) `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(true) @@ -187,7 +184,7 @@ class ControlsComponentTest : SysuiTestCase() { lockPatternUtils, keyguardStateController, userTracker, - secureSettings, + controlsSettingsRepository, optionalControlsTileResourceConfiguration ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt index cda701819d60..9fa7db127e1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.statusbar.policy.FlashlightController import com.android.systemui.utils.leaks.FakeFlashlightController import com.android.systemui.utils.leaks.LeakCheckedTest +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -38,156 +39,177 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() { @Mock private lateinit var context: Context private lateinit var flashlightController: FakeFlashlightController - private lateinit var underTest : FlashlightQuickAffordanceConfig + private lateinit var underTest: FlashlightQuickAffordanceConfig @Before fun setUp() { injectLeakCheckedDependency(FlashlightController::class.java) MockitoAnnotations.initMocks(this) - flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController + flashlightController = + SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) + as FakeFlashlightController underTest = FlashlightQuickAffordanceConfig(context, flashlightController) } @Test fun `flashlight is off -- triggered -- icon is on and active`() = runTest { - //given + // given flashlightController.isEnabled = false flashlightController.isAvailable = true val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when underTest.onTriggered(null) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_on, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_on, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = true val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when underTest.onTriggered(null) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_off, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightError() val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_off, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight availability now off -- hidden`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(false) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) job.cancel() } @Test fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(true) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active) - assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res) + assertTrue( + (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState + is ActivationState.Active + ) + assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res) job.cancel() } @Test fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest { - //given + // given flashlightController.isEnabled = false flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(true) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive) - assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res) + assertTrue( + (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState + is ActivationState.Inactive + ) + assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res) job.cancel() } @Test fun `flashlight available -- picker state default`() = runTest { - //given + // given flashlightController.isAvailable = true - //when + // when val result = underTest.getPickerScreenState() - //then + // then assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default) } @Test fun `flashlight not available -- picker state unavailable`() = runTest { - //given + // given flashlightController.isAvailable = false - //when + // when val result = underTest.getPickerScreenState() - //then + // then assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 9d518df5ccbe..ec3962c282f6 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -228,7 +228,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } @Nullable - static Transition fromBinder(@NonNull IBinder token) { + static Transition fromBinder(@Nullable IBinder token) { + if (token == null) return null; try { return ((Token) token).mTransition.get(); } catch (ClassCastException e) { |