Controls in QS

1. Migrate Android 11 users to a priority QS tile position
2. New Android 12 control users will see a tile appear at the end,
with seeding already initiated.
3. Add a new method to QSTileHost to position a tile in the middle
4. Remove all Controls code from GlobalActionsDialog

Bug: 172360102
Test: atest QSTileHostTest TileServicesTest QSFragmentTest
GlobalActionsDialogTest DeviceControlsTileTest

Change-Id: I1e52681b2cf7948b732808b1ff426d09b379ed93
This commit is contained in:
Matt Pietal
2021-04-02 07:41:19 -04:00
parent b0ccf6470f
commit ffb1601d49
26 changed files with 616 additions and 419 deletions

View File

@@ -757,6 +757,7 @@
android:excludeFromRecents="true"
android:showWhenLocked="true"
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">

View File

@@ -30,12 +30,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/global_actions_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>

View File

@@ -44,8 +44,9 @@ import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.globalactions.GlobalActionsDialog
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -198,11 +199,11 @@ class ControlsControllerImpl @Inject constructor (
// When a component is uninstalled, allow seeding to happen again if the user
// reinstalls the app
val prefs = userStructure.userContext.getSharedPreferences(
GlobalActionsDialog.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val completedSeedingPackageSet = prefs.getStringSet(
GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
prefs.edit().putStringSet(GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED,
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
completedSeedingPackageSet.intersect(servicePackageSet)).apply()
var changed = false

View File

@@ -16,6 +16,7 @@
package com.android.systemui.controls.management
import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
@@ -34,7 +35,6 @@ import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
@@ -45,7 +45,6 @@ import javax.inject.Inject
class ControlsEditingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
private val broadcastDispatcher: BroadcastDispatcher,
private val globalActionsComponent: GlobalActionsComponent,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -62,7 +61,6 @@ class ControlsEditingActivity @Inject constructor(
private lateinit var model: FavoritesModel
private lateinit var subtitle: TextView
private lateinit var saveButton: View
private var backToGlobalActions = true
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -86,11 +84,6 @@ class ControlsEditingActivity @Inject constructor(
structure = it
} ?: run(this::finish)
backToGlobalActions = intent.getBooleanExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
true
)
bindViews()
bindButtons()
@@ -109,15 +102,6 @@ class ControlsEditingActivity @Inject constructor(
}
override fun onBackPressed() {
if (backToGlobalActions) {
globalActionsComponent.handleShowGlobalActionsMenu()
} else {
val i = Intent().apply {
component = ComponentName(applicationContext, ControlsActivity::class.java)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(i)
}
animateExitAndFinish()
}
@@ -161,8 +145,12 @@ class ControlsEditingActivity @Inject constructor(
setText(R.string.save)
setOnClickListener {
saveFavorites()
startActivity(
Intent(applicationContext, ControlsActivity::class.java),
ActivityOptions
.makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()
)
animateExitAndFinish()
globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}

View File

@@ -43,7 +43,6 @@ import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import java.text.Collator
@@ -56,7 +55,6 @@ class ControlsFavoritingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
private val broadcastDispatcher: BroadcastDispatcher,
private val globalActionsComponent: GlobalActionsComponent,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -92,7 +90,6 @@ class ControlsFavoritingActivity @Inject constructor(
private lateinit var comparator: Comparator<StructureContainer>
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
private var backToGlobalActions = true
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -133,11 +130,6 @@ class ControlsFavoritingActivity @Inject constructor(
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
backToGlobalActions = intent.getBooleanExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
true
)
bindViews()
}
@@ -311,13 +303,6 @@ class ControlsFavoritingActivity @Inject constructor(
private fun bindButtons() {
otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
setOnClickListener {
val i = Intent().apply {
component = ComponentName(context, ControlsProviderSelectorActivity::class.java)
putExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
backToGlobalActions
)
}
if (doneButton.isEnabled) {
// The user has made changes
Toast.makeText(
@@ -326,8 +311,11 @@ class ControlsFavoritingActivity @Inject constructor(
Toast.LENGTH_SHORT
).show()
}
startActivity(i, ActivityOptions
.makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle())
startActivity(
Intent(context, ControlsProviderSelectorActivity::class.java),
ActivityOptions
.makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
)
animateExitAndFinish()
}
}
@@ -349,15 +337,10 @@ class ControlsFavoritingActivity @Inject constructor(
}
private fun openControlsOrigin() {
if (backToGlobalActions) {
globalActionsComponent.handleShowGlobalActionsMenu()
} else {
val i = Intent().apply {
component = ComponentName(applicationContext, ControlsActivity::class.java)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(i)
}
startActivity(
Intent(applicationContext, ControlsActivity::class.java),
ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
)
}
override fun onPause() {

View File

@@ -36,7 +36,6 @@ import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import java.util.concurrent.Executor
@@ -50,7 +49,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
private val globalActionsComponent: GlobalActionsComponent,
private val broadcastDispatcher: BroadcastDispatcher,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -59,7 +57,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
private const val TAG = "ControlsProviderSelectorActivity"
}
private var backToGlobalActions = true
private lateinit var recyclerView: RecyclerView
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = listingController.currentUserId
@@ -105,23 +102,13 @@ class ControlsProviderSelectorActivity @Inject constructor(
}
}
requireViewById<View>(R.id.done).visibility = View.GONE
backToGlobalActions = intent.getBooleanExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
true
)
}
override fun onBackPressed() {
if (backToGlobalActions) {
globalActionsComponent.handleShowGlobalActionsMenu()
} else {
val i = Intent().apply {
component = ComponentName(applicationContext, ControlsActivity::class.java)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(i)
val i = Intent().apply {
component = ComponentName(applicationContext, ControlsActivity::class.java)
}
startActivity(i, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
animateExitAndFinish()
}
@@ -169,10 +156,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
listingController.getAppLabel(it))
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
putExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
backToGlobalActions
)
}
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
animateExitAndFinish()

View File

@@ -26,7 +26,7 @@ import android.service.controls.Control
interface ControlActionCoordinator {
// If launched from an Activity, continue within this stack
var activityContext: Context?
var activityContext: Context
/**
* Close any dialogs which may have been open

View File

@@ -63,7 +63,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
get() = !keyguardStateController.isUnlocked()
override var activityContext: Context? = null
override lateinit var activityContext: Context
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
@@ -153,14 +153,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
// pending actions will only run after the control state has been refreshed
pendingAction = action
}
val wasLocked = isLocked
activityStarter.dismissKeyguardThenExecute({
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
if (wasLocked && activityContext == null) {
globalActionsComponent.handleShowGlobalActionsMenu()
} else {
action.invoke()
}
action.invoke()
true
}, { pendingAction = null }, true /* afterKeyguardGone */)
} else {

View File

@@ -41,6 +41,14 @@ class ControlsActivity @Inject constructor(
setContentView(R.layout.controls_fullscreen)
getLifecycle().addObserver(
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.control_detail_root),
window,
intent
)
)
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
setOnApplyWindowInsetsListener {
v: View, insets: WindowInsets ->
@@ -61,7 +69,7 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
uiController.show(parent, { animateExitAndFinish() }, this)
uiController.show(parent, { finish() }, this)
}
override fun onResume() {
@@ -71,7 +79,7 @@ class ControlsActivity @Inject constructor(
}
override fun onBackPressed() {
animateExitAndFinish()
finish()
}
override fun onStop() {
@@ -79,8 +87,4 @@ class ControlsActivity @Inject constructor(
uiController.hide()
}
private fun animateExitAndFinish() {
ControlsAnimations.exitAnimation(parent, { finish() }).start()
}
}

View File

@@ -28,10 +28,9 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
public const val EXTRA_ANIMATE = "extra_animate"
public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions"
}
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context?)
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
fun hide()
/**

View File

@@ -19,6 +19,8 @@ package com.android.systemui.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -129,6 +131,7 @@ class ControlsUiControllerImpl @Inject constructor (
override val available: Boolean
get() = controlsController.get().available
private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
private fun createCallback(
@@ -153,11 +156,12 @@ class ControlsUiControllerImpl @Inject constructor (
override fun show(
parent: ViewGroup,
onDismiss: Runnable,
activityContext: Context?
activityContext: Context
) {
Log.d(ControlsUiController.TAG, "show()")
this.parent = parent
this.onDismiss = onDismiss
this.activityContext = activityContext
hidden = false
retainCache = false
@@ -198,7 +202,7 @@ class ControlsUiControllerImpl @Inject constructor (
controlViewsById.clear()
controlsById.clear()
show(parent, onDismiss, controlActionCoordinator.activityContext)
show(parent, onDismiss, activityContext)
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
showAnim.setDuration(FADE_IN_MILLIS)
@@ -220,7 +224,7 @@ class ControlsUiControllerImpl @Inject constructor (
inflater.inflate(R.layout.controls_no_favorites, parent, true)
val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup
viewGroup.setOnClickListener { v: View -> startProviderSelectorActivity(v.context) }
viewGroup.setOnClickListener { _: View -> startProviderSelectorActivity() }
val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle)
subtitle.setText(context.resources.getString(R.string.quick_controls_subtitle))
@@ -234,20 +238,18 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
private fun startFavoritingActivity(context: Context, si: StructureInfo) {
startTargetedActivity(context, si, ControlsFavoritingActivity::class.java)
private fun startFavoritingActivity(si: StructureInfo) {
startTargetedActivity(si, ControlsFavoritingActivity::class.java)
}
private fun startEditingActivity(context: Context, si: StructureInfo) {
startTargetedActivity(context, si, ControlsEditingActivity::class.java)
private fun startEditingActivity(si: StructureInfo) {
startTargetedActivity(si, ControlsEditingActivity::class.java)
}
private fun startTargetedActivity(context: Context, si: StructureInfo, klazz: Class<*>) {
val i = Intent(context, klazz).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
val i = Intent(activityContext, klazz)
putIntentExtras(i, si)
startActivity(context, i)
startActivity(i)
retainCache = true
}
@@ -261,27 +263,22 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
private fun startProviderSelectorActivity(context: Context) {
val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(context, i)
private fun startProviderSelectorActivity() {
startActivity(Intent(activityContext, ControlsProviderSelectorActivity::class.java))
}
private fun startActivity(context: Context, intent: Intent) {
private fun startActivity(intent: Intent) {
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
intent.putExtra(
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
controlActionCoordinator.activityContext == null
)
onDismiss.run()
activityStarter.dismissKeyguardThenExecute({
shadeController.collapsePanel(false)
context.startActivity(intent)
true
}, null, true)
if (keyguardStateController.isUnlocked()) {
activityContext.startActivity(
intent,
ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
)
} else {
activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
}
}
private fun showControlsView(items: List<SelectionItem>) {
@@ -328,9 +325,9 @@ class ControlsUiControllerImpl @Inject constructor (
) {
when (pos) {
// 0: Add Control
0 -> startFavoritingActivity(view.context, selectedStructure)
0 -> startFavoritingActivity(selectedStructure)
// 1: Edit controls
1 -> startEditingActivity(view.context, selectedStructure)
1 -> startEditingActivity(selectedStructure)
}
dismiss()
}
@@ -399,15 +396,9 @@ class ControlsUiControllerImpl @Inject constructor (
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
if (controlActionCoordinator.activityContext == null) {
parent.requireViewById<View>(R.id.controls_spacer).apply {
visibility = View.VISIBLE
}
} else {
parent.requireViewById<ImageView>(R.id.controls_close).apply {
setOnClickListener { _: View -> onDismiss.run() }
visibility = View.VISIBLE
}
parent.requireViewById<ImageView>(R.id.controls_close).apply {
setOnClickListener { _: View -> onDismiss.run() }
visibility = View.VISIBLE
}
val maxColumns = findMaxColumns()
@@ -522,8 +513,6 @@ class ControlsUiControllerImpl @Inject constructor (
override fun hide() {
hidden = true
controlActionCoordinator.activityContext = null
closeDialogs(true)
controlsController.get().unsubscribe()

View File

@@ -17,8 +17,6 @@ package com.android.systemui.globalactions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import android.animation.Animator;
@@ -30,10 +28,8 @@ import android.app.IActivityManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
@@ -47,7 +43,6 @@ import android.telecom.TelecomManager;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.Log;
import android.view.IWindowManager;
import android.view.View;
import android.view.ViewGroup;
@@ -69,18 +64,12 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsAnimations;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -91,12 +80,6 @@ import com.android.systemui.util.leak.RotationUtils;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -105,7 +88,7 @@ import javax.inject.Provider;
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
* on whether the keyguard is showing, and whether the device is provisioned.
* This version includes wallet and controls.
* This version includes wallet.
*/
public class GlobalActionsDialog extends GlobalActionsDialogLite
implements DialogInterface.OnDismissListener,
@@ -116,10 +99,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
private static final String TAG = "GlobalActionsDialog";
public static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted";
public static final String PREFS_CONTROLS_FILE = "controls_prefs";
private static final int SEEDING_MAX = 2;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeDepthController mDepthController;
@@ -129,13 +108,9 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
private final IStatusBarService mStatusBarService;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private GlobalActionsPanelPlugin mWalletPlugin;
private Optional<ControlsUiController> mControlsUiControllerOptional;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
private ControlsComponent mControlsComponent;
private Optional<ControlsController> mControlsControllerOptional;
private UserContextProvider mUserContextProvider;
@VisibleForTesting
boolean mShowLockScreenCardsAndControls = false;
boolean mShowLockScreenCards = false;
/**
* @param context everything needs a context :(
@@ -158,9 +133,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
UiEventLogger uiEventLogger,
RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
ControlsComponent controlsComponent,
UserContextProvider userContextProvider) {
RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
super(context, windowManagerFuncs,
audioManager, iDreamManager,
@@ -186,11 +159,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
mControlsComponent = controlsComponent;
mControlsUiControllerOptional = controlsComponent.getControlsUiController();
mControlsControllerOptional = controlsComponent.getControlsController();
mSysUiState = sysUiState;
mUserContextProvider = userContextProvider;
mActivityStarter = activityStarter;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -201,10 +170,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
if (dialog.mWalletViewController != null) {
dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
}
if (!dialog.isShowingControls()
&& mControlsComponent.getVisibility() == AVAILABLE) {
dialog.showControls(mControlsUiControllerOptional.get());
}
if (unlocked) {
dialog.hideLockMessage();
}
@@ -212,25 +178,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
}
});
if (mControlsComponent.getControlsListingController().isPresent()) {
mControlsComponent.getControlsListingController().get()
.addCallback(list -> {
mControlsServiceInfos = list;
// This callback may occur after the dialog has been shown. If so, add
// controls into the already visible space or show the lock msg if needed.
if (mDialog != null) {
ActionsDialog dialog = (ActionsDialog) mDialog;
if (!dialog.isShowingControls()
&& mControlsComponent.getVisibility() == AVAILABLE) {
dialog.showControls(mControlsUiControllerOptional.get());
} else if (shouldShowLockMessage(dialog)) {
dialog.showLockMessage();
}
}
});
}
// Listen for changes to show controls on the power menu while locked
// Listen for changes to show pay on the power menu while locked
onPowerMenuLockScreenSettingsChanged();
mGlobalSettings.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
@@ -243,65 +191,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
});
}
/**
* See if any available control service providers match one of the preferred components. If
* they do, and there are no current favorites for that component, query the preferred
* component for a limited number of suggested controls.
*/
private void seedFavorites() {
if (!mControlsControllerOptional.isPresent()
|| mControlsServiceInfos.isEmpty()) {
return;
}
String[] preferredControlsPackages = getContext().getResources()
.getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages);
SharedPreferences prefs = mUserContextProvider.getUserContext()
.getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
Collections.emptySet());
List<ComponentName> componentsToSeed = new ArrayList<>();
for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) {
String pkg = preferredControlsPackages[i];
for (ControlsServiceInfo info : mControlsServiceInfos) {
if (!pkg.equals(info.componentName.getPackageName())) continue;
if (seededPackages.contains(pkg)) {
break;
} else if (mControlsControllerOptional.get()
.countFavoritesForComponent(info.componentName) > 0) {
// When there are existing controls but no saved preference, assume it
// is out of sync, perhaps through a device restore, and update the
// preference
addPackageToSeededSet(prefs, pkg);
break;
}
componentsToSeed.add(info.componentName);
break;
}
}
if (componentsToSeed.isEmpty()) return;
mControlsControllerOptional.get().seedFavoritesForComponents(
componentsToSeed,
(response) -> {
Log.d(TAG, "Controls seeded: " + response);
if (response.getAccepted()) {
addPackageToSeededSet(prefs, response.getPackageName());
}
});
}
private void addPackageToSeededSet(SharedPreferences prefs, String pkg) {
Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
Collections.emptySet());
Set<String> updatedPkgs = new HashSet<>(seededPackages);
updatedPkgs.add(pkg);
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply();
}
/**
* Show the global actions dialog (creating if necessary)
*
@@ -313,12 +202,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
super.showOrHideDialog(keyguardShowing, isDeviceProvisioned);
}
@Override
protected void handleShow() {
seedFavorites();
super.handleShow();
}
/**
* Returns the maximum number of power menu items to show based on which GlobalActions
* layout is being used.
@@ -340,14 +223,9 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
initDialogItems();
mDepthController.setShowingHomeControls(true);
ControlsUiController uiController = null;
if (mControlsComponent.getVisibility() == AVAILABLE) {
uiController = mControlsUiControllerOptional.get();
}
ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
this::getWalletViewController, mDepthController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
controlsAvailable(), uiController,
mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter);
if (shouldShowLockMessage(dialog)) {
@@ -407,10 +285,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
@Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
private ResetOrientationData mResetOrientationData;
private final boolean mControlsAvailable;
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
@VisibleForTesting ViewGroup mLockMessageContainer;
private TextView mLockMessage;
@@ -419,15 +293,12 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
boolean controlsAvailable, @Nullable ControlsUiController controlsUiController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
adapter, overflowAdapter, depthController, sysuiColorExtractor,
statusBarService, notificationShadeWindowController, sysuiState,
onRotateCallback, keyguardShowing, powerAdapter);
mControlsAvailable = controlsAvailable;
mControlsUiController = controlsUiController;
mWalletFactory = walletFactory;
// Update window attributes
@@ -448,16 +319,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
initializeLayout();
}
private boolean isShowingControls() {
return mControlsUiController != null;
}
private void showControls(ControlsUiController controller) {
mControlsUiController = controller;
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
null /* activityContext */);
}
private boolean isWalletViewAvailable() {
return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
}
@@ -524,10 +385,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
if (!mControlsAvailable) {
panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
com.android.systemui.R.dimen.global_actions_wallet_top_margin);
}
panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
com.android.systemui.R.dimen.global_actions_wallet_top_margin);
View walletView = mWalletViewController.getPanelContent();
panelContainer.addView(walletView, panelParams);
// Smooth transitions when wallet is resized, which can happen when a card is added
@@ -554,7 +413,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
@Override
protected void initializeLayout() {
super.initializeLayout();
mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
mLockMessageContainer = requireViewById(
com.android.systemui.R.id.global_actions_lock_message_container);
mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);
@@ -577,10 +435,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
windowInsets.getStableInsetBottom());
return WindowInsets.CONSUMED;
});
if (mControlsUiController != null) {
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
null /* activityContext */);
}
mBackgroundDrawable.setAlpha(0);
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
@@ -609,20 +463,11 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
@Override
protected void dismissInternal() {
super.dismissInternal();
if (mControlsUiController != null) mControlsUiController.closeDialogs(false);
}
private void dismissForControlsActivity() {
dismissWithAnimation(() -> {
ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
});
}
@Override
protected void completeDismiss() {
dismissWallet();
if (mControlsUiController != null) mControlsUiController.hide();
resetOrientation();
super.completeDismiss();
}
@@ -647,15 +492,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
public void refreshDialog() {
// ensure dropdown menus are dismissed before re-initializing the dialog
dismissWallet();
if (mControlsUiController != null) {
mControlsUiController.hide();
}
super.refreshDialog();
if (mControlsUiController != null) {
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
null /* activityContext */);
}
}
void hideLockMessage() {
@@ -699,15 +536,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
return isPanelDebugModeEnabled(context);
}
private boolean controlsAvailable() {
return isDeviceProvisioned()
&& mControlsComponent.isEnabled()
&& !mControlsServiceInfos.isEmpty();
}
private boolean shouldShowLockMessage(ActionsDialog dialog) {
return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
|| isWalletAvailableAfterUnlock(dialog);
return isWalletAvailableAfterUnlock(dialog);
}
// Temporary while we move items out of the power menu
@@ -715,12 +545,12 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
== STRONG_AUTH_REQUIRED_AFTER_BOOT;
return !mKeyguardStateController.isUnlocked()
&& (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
&& (!mShowLockScreenCards || isLockedAfterBoot)
&& dialog.isWalletViewAvailable();
}
private void onPowerMenuLockScreenSettingsChanged() {
mShowLockScreenCardsAndControls = mSecureSettings.getInt(
mShowLockScreenCards = mSecureSettings.getInt(
Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
}
}

View File

@@ -23,7 +23,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
@@ -57,6 +56,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.settings.SecureSettings;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -79,6 +79,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
public static final int POSITION_AT_END = -1;
public static final String TILES_SETTING = Secure.QS_TILES;
private final Context mContext;
@@ -101,6 +102,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
@Inject
public QSTileHost(Context context,
@@ -116,7 +118,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
Optional<StatusBar> statusBarOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
UserTracker userTracker) {
UserTracker userTracker,
SecureSettings secureSettings) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -135,6 +138,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
pluginManager.addPluginListener(this, QSFactory.class, true);
mDumpManager.registerDumpable(TAG, this);
mUserTracker = userTracker;
mSecureSettings = secureSettings;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -343,19 +347,43 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
}
/**
* Add a tile to the end
*
* @param spec string matching a pre-defined tilespec
*/
public void addTile(String spec) {
changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec));
addTile(spec, POSITION_AT_END);
}
private void saveTilesToSettings(List<String> tileSpecs) {
Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
TextUtils.join(",", tileSpecs), null /* tag */,
false /* default */, mCurrentUser, true /* overrideable by restore */);
/**
* Add a tile into the requested spot, or at the end if the position is greater than the number
* of tiles.
* @param spec string matching a pre-defined tilespec
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
changeTileSpecs(tileSpecs -> {
if (tileSpecs.contains(spec)) return false;
int size = tileSpecs.size();
if (requestPosition == POSITION_AT_END || requestPosition >= size) {
tileSpecs.add(spec);
} else {
tileSpecs.add(requestPosition, spec);
}
return true;
});
}
void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
}
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
TILES_SETTING, mCurrentUser);
final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
if (changeFunction.test(tileSpecs)) {
saveTilesToSettings(tileSpecs);

View File

@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.util.settings.SecureSettings;
@@ -61,6 +62,7 @@ public interface QSModule {
NightDisplayListener nightDisplayListener,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
AutoTileManager manager = new AutoTileManager(
context,
@@ -74,6 +76,7 @@ public interface QSModule {
nightDisplayListener,
castController,
reduceBrightColorsController,
deviceControlsController,
isReduceBrightColorsAvailable
);
manager.init();

View File

@@ -39,8 +39,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.util.settings.GlobalSettings
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@@ -53,9 +51,7 @@ class DeviceControlsTile @Inject constructor(
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
qsLogger: QSLogger,
private val controlsComponent: ControlsComponent,
private val featureFlags: FeatureFlags,
globalSettings: GlobalSettings
private val controlsComponent: ControlsComponent
) : QSTileImpl<QSTile.State>(
host,
backgroundLooper,
@@ -67,11 +63,6 @@ class DeviceControlsTile @Inject constructor(
qsLogger
) {
companion object {
const val SETTINGS_FLAG = "controls_lockscreen"
}
private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
private var hasControlsApps = AtomicBoolean(false)
private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
@@ -92,9 +83,7 @@ class DeviceControlsTile @Inject constructor(
}
override fun isAvailable(): Boolean {
return featureFlags.isKeyguardLayoutEnabled &&
controlsLockscreen &&
controlsComponent.getControlsController().isPresent
return controlsComponent.getControlsController().isPresent
}
override fun newTileState(): QSTile.State {
@@ -114,7 +103,7 @@ class DeviceControlsTile @Inject constructor(
val i = Intent().apply {
component = ComponentName(mContext, ControlsActivity::class.java)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
putExtra(ControlsUiController.EXTRA_ANIMATE, true)
}
mContext.startActivity(i)
}

View File

@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
import com.android.systemui.util.UserAwareController;
@@ -58,6 +59,7 @@ public class AutoTileManager implements UserAwareController {
public static final String WORK = "work";
public static final String NIGHT = "night";
public static final String CAST = "cast";
public static final String DEVICE_CONTROLS = "controls";
public static final String BRIGHTNESS = "reduce_brightness";
static final String SETTING_SEPARATOR = ":";
@@ -74,6 +76,7 @@ public class AutoTileManager implements UserAwareController {
private final ManagedProfileController mManagedProfileController;
private final NightDisplayListener mNightDisplayListener;
private final CastController mCastController;
private final DeviceControlsController mDeviceControlsController;
private final ReduceBrightColorsController mReduceBrightColorsController;
private final boolean mIsReduceBrightColorsAvailable;
private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
@@ -88,6 +91,7 @@ public class AutoTileManager implements UserAwareController {
NightDisplayListener nightDisplayListener,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
mContext = context;
mHost = host;
@@ -102,6 +106,7 @@ public class AutoTileManager implements UserAwareController {
mCastController = castController;
mReduceBrightColorsController = reduceBrightColorsController;
mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
mDeviceControlsController = deviceControlsController;
}
/**
@@ -138,6 +143,9 @@ public class AutoTileManager implements UserAwareController {
if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
}
if (!mAutoTracker.isAdded(DEVICE_CONTROLS)) {
mDeviceControlsController.setCallback(mDeviceControlsCallback);
}
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
@@ -158,6 +166,7 @@ public class AutoTileManager implements UserAwareController {
mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
}
mCastController.removeCallback(mCastCallback);
mDeviceControlsController.removeCallback();
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
mAutoAddSettingList.get(i).setListening(false);
@@ -274,6 +283,17 @@ public class AutoTileManager implements UserAwareController {
}
};
private final DeviceControlsController.Callback mDeviceControlsCallback =
new DeviceControlsController.Callback() {
@Override
public void onControlsAvailable(int position) {
if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
mHost.addTile(DEVICE_CONTROLS, position);
mAutoTracker.setTileAdded(DEVICE_CONTROLS);
mHandler.post(() -> mDeviceControlsController.removeCallback());
}
};
@VisibleForTesting
final NightDisplayListener.Callback mNightDisplayCallback =
new NightDisplayListener.Callback() {

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 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.policy
/**
* Supports adding a DeviceControls QS tile
*/
interface DeviceControlsController {
interface Callback {
/**
* If controls become available, initiate this callback with the desired position
*/
fun onControlsAvailable(position: Int)
}
/** Add callback, supporting only a single callback at once */
fun setCallback(callback: Callback)
/** Remove any existing callback, if any */
fun removeCallback()
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2021 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.policy
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.QSTileHost.POSITION_AT_END
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
import javax.inject.Inject
/**
* Watches for Device Controls QS Tile activation, which can happen in two ways:
* <ol>
* <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
* priority position.
* <li>Device controls service becomes available - For non-migrated users, create a tile and
* place at the end of active tiles, and initiate seeding where possible.
* </ol>
*/
@SysUISingleton
public class DeviceControlsControllerImpl @Inject constructor(
private val context: Context,
private val controlsComponent: ControlsComponent,
private val userContextProvider: UserContextProvider
) : DeviceControlsController {
private var callback: Callback? = null
internal var position: Int? = null
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
if (!serviceInfos.isEmpty()) {
seedFavorites(serviceInfos)
}
}
}
companion object {
private const val TAG = "DeviceControlsControllerImpl"
internal const val QS_PRIORITY_POSITION = 3
internal const val QS_DEFAULT_POSITION = POSITION_AT_END
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
internal const val PREFS_CONTROLS_FILE = "controls_prefs"
private const val SEEDING_MAX = 2
}
private fun checkMigrationToQs() {
controlsComponent.getControlsController().ifPresent {
if (!it.getFavorites().isEmpty()) {
position = QS_PRIORITY_POSITION
}
}
}
/**
* This migration logic assumes that something like [AutoTileManager] is tracking state
* externally, and won't call this method after receiving a response via
* [Callback#onControlsAvailable], once per user. Otherwise the calculated position may be
* incorrect.
*/
override fun setCallback(callback: Callback) {
// Treat any additional call as a reset before recalculating
removeCallback()
checkMigrationToQs()
controlsComponent.getControlsListingController().ifPresent {
it.addCallback(listingCallback)
}
this.callback = callback
fireControlsAvailable()
}
override fun removeCallback() {
position = null
callback = null
controlsComponent.getControlsListingController().ifPresent {
it.removeCallback(listingCallback)
}
}
private fun fireControlsAvailable() {
position?.let {
Log.i(TAG, "Setting DeviceControlsTile position: $it")
callback?.onControlsAvailable(it)
}
}
/**
* See if any available control service providers match one of the preferred components. If
* they do, and there are no current favorites for that component, query the preferred
* component for a limited number of suggested controls.
*/
private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
val preferredControlsPackages = context.getResources().getStringArray(
R.array.config_controlsPreferredPackages)
val prefs = userContextProvider.userContext.getSharedPreferences(
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
val controlsController = controlsComponent.getControlsController().get()
val componentsToSeed = mutableListOf<ComponentName>()
var i = 0
while (i < Math.min(SEEDING_MAX, preferredControlsPackages.size)) {
val pkg = preferredControlsPackages[i]
serviceInfos.forEach {
if (pkg.equals(it.componentName.packageName) && !seededPackages.contains(pkg)) {
if (controlsController.countFavoritesForComponent(it.componentName) > 0) {
// When there are existing controls but no saved preference, assume it
// is out of sync, perhaps through a device restore, and update the
// preference
addPackageToSeededSet(prefs, pkg)
} else {
componentsToSeed.add(it.componentName)
}
}
}
i++
}
if (componentsToSeed.isEmpty()) return
controlsController.seedFavoritesForComponents(
componentsToSeed,
{ response ->
Log.d(TAG, "Controls seeded: $response")
if (response.accepted) {
addPackageToSeededSet(prefs, response.packageName)
if (position == null) {
position = QS_DEFAULT_POSITION
}
fireControlsAvailable()
controlsComponent.getControlsListingController().ifPresent {
it.removeCallback(listingCallback)
}
}
})
}
private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
val updatedPkgs = seededPackages.toMutableSet()
updatedPkgs.add(pkg)
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
}
}

View File

@@ -26,6 +26,8 @@ import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -44,8 +46,6 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -113,6 +113,11 @@ public interface StatusBarPolicyModule {
@Binds
ZenModeController provideZenModeController(ZenModeControllerImpl controllerImpl);
/** */
@Binds
DeviceControlsController provideDeviceControlsController(
DeviceControlsControllerImpl controllerImpl);
/** */
@Binds
NetworkController.AccessPointController provideAccessPointController(

View File

@@ -58,15 +58,10 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -118,11 +113,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private IStatusBarService mStatusBarService;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private ControlsUiController mControlsUiController;
@Mock private IWindowManager mWindowManager;
@Mock private Executor mBackgroundExecutor;
@Mock private ControlsListingController mControlsListingController;
@Mock private ControlsController mControlsController;
@Mock private UiEventLogger mUiEventLogger;
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
@@ -130,10 +122,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
@Mock GlobalActionsPanelPlugin mWalletPlugin;
@Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private UserTracker mUserTracker;
@Mock private SecureSettings mSecureSettings;
private ControlsComponent mControlsComponent;
private TestableLooper mTestableLooper;
@@ -144,18 +134,6 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
allowTestableLooperAsMainThread();
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
when(mUserContextProvider.getUserContext()).thenReturn(mContext);
mControlsComponent = new ControlsComponent(
true,
mContext,
() -> mControlsController,
() -> mControlsUiController,
() -> mControlsListingController,
mLockPatternUtils,
mKeyguardStateController,
mUserTracker,
mSecureSettings
);
mGlobalActionsDialog = new GlobalActionsDialog(mContext,
mWindowManagerFuncs,
@@ -186,9 +164,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
mUiEventLogger,
mRingerModeTracker,
mSysUiState,
mHandler,
mControlsComponent,
mUserContextProvider
mHandler
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
@@ -511,7 +487,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
mGlobalActionsDialog.mShowLockScreenCardsAndControls = false;
mGlobalActionsDialog.mShowLockScreenCards = false;
setupDefaultActions();
when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
@@ -528,14 +504,14 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
}
@Test
public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen()
public void testShouldNotShowLockScreenMessage_whenWalletShownOnLockScreen()
throws RemoteException {
mGlobalActionsDialog = spy(mGlobalActionsDialog);
mGlobalActionsDialog.mDialog = null;
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
mGlobalActionsDialog.mShowLockScreenCardsAndControls = true;
mGlobalActionsDialog.mShowLockScreenCards = true;
setupDefaultActions();
when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
@@ -552,7 +528,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
}
@Test
public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled()
public void testShouldNotShowLockScreenMessage_whenWalletBothDisabled()
throws RemoteException {
mGlobalActionsDialog = spy(mGlobalActionsDialog);
mGlobalActionsDialog.mDialog = null;
@@ -560,11 +536,10 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
mGlobalActionsDialog.mShowLockScreenCardsAndControls = true;
mGlobalActionsDialog.mShowLockScreenCards = true;
setupDefaultActions();
when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
when(mWalletController.getPanelContent()).thenReturn(null);
when(mControlsUiController.getAvailable()).thenReturn(false);
mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);

View File

@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Ignore;
@@ -130,7 +131,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mock(PluginManager.class), mock(TunerService.class),
() -> mock(AutoTileManager.class), mock(DumpManager.class),
mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class));
mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
mock(SecureSettings.class));
qs.setHost(host);
qs.setListening(true);

View File

@@ -21,20 +21,21 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -64,11 +65,13 @@ import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
@@ -78,7 +81,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Provider;
@@ -119,6 +121,8 @@ public class QSTileHostTest extends SysuiTestCase {
private UiEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
@Mock
private SecureSettings mSecureSettings;
private Handler mHandler;
private TestableLooper mLooper;
@@ -138,11 +142,12 @@ public class QSTileHostTest extends SysuiTestCase {
mHandler = new Handler(mLooper.getLooper());
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker);
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
mSecureSettings);
setUpTileFactory();
Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
"", ActivityManager.getCurrentUser());
when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
.thenReturn("");
}
@After
@@ -162,6 +167,8 @@ public class QSTileHostTest extends SysuiTestCase {
return new TestTile1(mQSTileHost);
} else if ("spec2".equals(spec)) {
return new TestTile2(mQSTileHost);
} else if ("spec3".equals(spec)) {
return new TestTile3(mQSTileHost);
} else if ("na".equals(spec)) {
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
@@ -232,6 +239,8 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testNoRepeatedSpecs_addTile() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
mQSTileHost.addTile("spec1");
@@ -241,6 +250,48 @@ public class QSTileHostTest extends SysuiTestCase {
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
}
@Test
public void testAddTileAtValidPosition() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
mQSTileHost.addTile("spec2", 1);
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
assertEquals("spec3", mQSTileHost.mTileSpecs.get(2));
}
@Test
public void testAddTileAtInvalidPositionAddsToEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
mQSTileHost.addTile("spec2", 100);
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
}
@Test
public void testAddTileAtEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
}
@Test
public void testNoRepeatedSpecs_customTile() {
mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC);
@@ -318,16 +369,17 @@ public class QSTileHostTest extends SysuiTestCase {
verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
}
private static class TestQSTileHost extends QSTileHost {
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
UiEventLogger uiEventLogger, UserTracker userTracker) {
UiEventLogger uiEventLogger, UserTracker userTracker,
SecureSettings secureSettings) {
super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
tunerService, autoTiles, dumpManager, broadcastDispatcher,
Optional.of(statusBar), qsLogger, uiEventLogger, userTracker);
Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings);
}
@Override
@@ -339,18 +391,16 @@ public class QSTileHostTest extends SysuiTestCase {
}
@Override
public void changeTiles(List<String> previousTiles, List<String> newTiles) {
String previousSetting = Settings.Secure.getStringForUser(
getContext().getContentResolver(), TILES_SETTING,
ActivityManager.getCurrentUser());
super.changeTiles(previousTiles, newTiles);
void saveTilesToSettings(List<String> tileSpecs) {
super.saveTilesToSettings(tileSpecs);
ArgumentCaptor<String> specs = ArgumentCaptor.forClass(String.class);
verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING),
specs.capture(), isNull(), eq(false), anyInt(), eq(true));
// After tiles are changed, make sure to call onTuningChanged with the new setting if it
// changed
String newSetting = Settings.Secure.getStringForUser(getContext().getContentResolver(),
TILES_SETTING, ActivityManager.getCurrentUser());
if (!Objects.equals(newSetting, previousSetting)) {
onTuningChanged(TILES_SETTING, newSetting);
}
onTuningChanged(TILES_SETTING, specs.getValue());
}
}
@@ -415,6 +465,13 @@ public class QSTileHostTest extends SysuiTestCase {
}
}
private class TestTile3 extends TestTile {
protected TestTile3(QSHost host) {
super(host);
}
}
private class NotAvailableTile extends TestTile {
protected NotAvailableTile(QSHost host) {

View File

@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -95,6 +96,8 @@ public class TileServicesTest extends SysuiTestCase {
private UiEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
@Mock
private SecureSettings mSecureSettings;
@Before
public void setUp() throws Exception {
@@ -114,7 +117,8 @@ public class TileServicesTest extends SysuiTestCase {
Optional.of(mStatusBar),
mQSLogger,
mUiEventLogger,
mUserTracker);
mUserTracker,
mSecureSettings);
mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
mUserTracker);
}

View File

@@ -38,11 +38,9 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -83,9 +81,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Mock
private lateinit var controlsController: ControlsController
@Mock
private lateinit var featureFlags: FeatureFlags
private lateinit var globalSettings: GlobalSettings
@Mock
private lateinit var serviceInfo: ControlsServiceInfo
@Mock
private lateinit var uiEventLogger: UiEventLogger
@@ -116,11 +111,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
setupControlsComponent()
globalSettings = FakeSettings()
globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
`when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
tile = createTile()
}
@@ -155,13 +145,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
assertThat(tile.isAvailable).isTrue()
}
@Test
fun testNotAvailableFeature() {
`when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
assertThat(tile.isAvailable).isFalse()
}
@Test
fun testNotAvailableControls() {
featureEnabled = false
@@ -178,14 +161,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
assertThat(tile.isAvailable).isTrue()
}
@Test
fun testNotAvailableControlsLockscreenFlag() {
globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
tile = createTile()
assertThat(tile.isAvailable).isFalse()
}
@Test
fun testObservingCallback() {
verify(controlsListingController).observe(
@@ -329,9 +304,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
statusBarStateController,
activityStarter,
qsLogger,
controlsComponent,
featureFlags,
globalSettings
controlsComponent
)
}
}

View File

@@ -54,6 +54,7 @@ import com.android.systemui.qs.SecureSetting;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -94,6 +95,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
@Mock private ManagedProfileController mManagedProfileController;
@Mock private NightDisplayListener mNightDisplayListener;
@Mock private ReduceBrightColorsController mReduceBrightColorsController;
@Mock private DeviceControlsController mDeviceControlsController;;
@Mock(answer = Answers.RETURNS_SELF)
private AutoAddTracker.Builder mAutoAddTrackerBuilder;
@Mock private Context mUserContext;
@@ -139,6 +141,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
NightDisplayListener nightDisplayListener,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost,
Handler.createAsync(TestableLooper.get(this).getLooper()),
@@ -149,13 +152,15 @@ public class AutoTileManagerTest extends SysuiTestCase {
nightDisplayListener,
castController,
reduceBrightColorsController,
deviceControlsController,
isReduceBrightColorsAvailable);
}
private AutoTileManager createAutoTileManager(Context context) {
return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
mDataSaverController, mManagedProfileController, mNightDisplayListener,
mCastController, mReduceBrightColorsController, mIsReduceBrightColorsAvailable);
mCastController, mReduceBrightColorsController, mDeviceControlsController,
mIsReduceBrightColorsAvailable);
}
@Test
@@ -169,10 +174,11 @@ public class AutoTileManagerTest extends SysuiTestCase {
NightDisplayListener nDS = mock(NightDisplayListener.class);
CastController cC = mock(CastController.class);
ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
DeviceControlsController dCC = mock(DeviceControlsController.class);
AutoTileManager manager =
createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
true);
dCC, true);
verify(tracker, never()).initialize();
verify(hC, never()).addCallback(any());
@@ -181,6 +187,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
verify(nDS, never()).setCallback(any());
verify(cC, never()).addCallback(any());
verify(rBC, never()).addCallback(any());
verify(dCC, never()).setCallback(any());
assertNull(manager.getSecureSettingForKey(TEST_SETTING));
assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
}
@@ -229,6 +236,10 @@ public class AutoTileManagerTest extends SysuiTestCase {
inOrderCast.verify(mCastController).removeCallback(any());
inOrderCast.verify(mCastController).addCallback(any());
InOrder inOrderDevices = inOrder(mDeviceControlsController);
inOrderDevices.verify(mDeviceControlsController).removeCallback();
inOrderDevices.verify(mDeviceControlsController).setCallback(any());
SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertTrue(setting.isListening());
@@ -273,6 +284,10 @@ public class AutoTileManagerTest extends SysuiTestCase {
inOrderCast.verify(mCastController).removeCallback(any());
inOrderCast.verify(mCastController, never()).addCallback(any());
InOrder inOrderDevices = inOrder(mDeviceControlsController);
inOrderDevices.verify(mDeviceControlsController).removeCallback();
inOrderDevices.verify(mDeviceControlsController).setCallback(any());
SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertFalse(setting.isListening());

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2021 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.policy
import android.content.ComponentName
import android.content.Context
import android.content.pm.ServiceInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.SeedResponse
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingController.ControlsListingCallback
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_DEFAULT_POSITION
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_PRIORITY_POSITION
import java.util.Optional
import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.ArgumentMatchers.anyInt
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DeviceControlsControllerImplTest : SysuiTestCase() {
@Mock
private lateinit var controlsComponent: ControlsComponent
@Mock
private lateinit var controlsController: ControlsController
@Mock
private lateinit var controlsListingController: ControlsListingController
@Mock
private lateinit var callback: DeviceControlsController.Callback
@Captor
private lateinit var listingCallbackCaptor: ArgumentCaptor<ControlsListingCallback>
@Mock
private lateinit var structureInfo: StructureInfo
@Mock
private lateinit var serviceInfo: ServiceInfo
@Mock
private lateinit var userContextProvider: UserContextProvider
@Captor
private lateinit var seedCallback: ArgumentCaptor<Consumer<SeedResponse>>
private lateinit var controlsServiceInfo: ControlsServiceInfo
private lateinit var controller: DeviceControlsControllerImpl
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
private val TEST_PKG = "test.pkg"
private val TEST_COMPONENT = ComponentName(TEST_PKG, "test.class")
}
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(controlsComponent.getControlsController())
.thenReturn(Optional.of(controlsController))
`when`(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
controller = DeviceControlsControllerImpl(mContext, controlsComponent, userContextProvider)
`when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
controlsServiceInfo = ControlsServiceInfo(mContext, serviceInfo)
`when`(userContextProvider.userContext).thenReturn(mContext)
mContext.getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE).edit()
.putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
.apply()
}
@Test
fun testNoCallbackWhenNoServicesAvailable() {
`when`(controlsController.getFavorites()).thenReturn(emptyList())
controller.setCallback(callback)
verify(controlsListingController).addCallback(capture(listingCallbackCaptor))
listingCallbackCaptor.value.onServicesUpdated(emptyList())
verify(callback, never()).onControlsAvailable(anyInt())
}
@Test
fun testSetPriorityPositionIsSetWhenFavoritesAreAvailable() {
`when`(controlsController.getFavorites()).thenReturn(listOf(structureInfo))
controller.setCallback(callback)
verify(controlsListingController).addCallback(capture(listingCallbackCaptor))
listingCallbackCaptor.value.onServicesUpdated(listOf(controlsServiceInfo))
verify(callback).onControlsAvailable(QS_PRIORITY_POSITION)
}
@Test
fun testSetDefaultPositionIsSetWhenNoFavoritesAreAvailable() {
`when`(controlsController.getFavorites()).thenReturn(emptyList())
controller.setCallback(callback)
mContext.getOrCreateTestableResources().addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PKG))
verify(controlsListingController).addCallback(capture(listingCallbackCaptor))
listingCallbackCaptor.value.onServicesUpdated(listOf(controlsServiceInfo))
verify(controlsController).seedFavoritesForComponents(
eq(listOf(TEST_COMPONENT)),
capture(seedCallback)
)
seedCallback.value.accept(SeedResponse(TEST_PKG, true))
verify(callback).onControlsAvailable(QS_DEFAULT_POSITION)
}
}