Merge "Device Control metrics" into sc-dev am: e52627847c
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13677132 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Id9eaa0725459c5d273e5e8a9279ec38ad438c045
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.controls
|
||||||
|
|
||||||
|
import android.service.controls.DeviceTypes.DeviceType
|
||||||
|
|
||||||
|
import com.android.internal.logging.UiEvent
|
||||||
|
import com.android.internal.logging.UiEventLogger
|
||||||
|
import com.android.systemui.controls.ui.ControlViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for logging UI events related to controls
|
||||||
|
*/
|
||||||
|
interface ControlsMetricsLogger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a new instance id for this controls session, defined as when the controls area is
|
||||||
|
* made visible to when it is closed.
|
||||||
|
*/
|
||||||
|
fun assignInstanceId()
|
||||||
|
|
||||||
|
fun touch(cvh: ControlViewHolder, isLocked: Boolean) {
|
||||||
|
log(ControlsEvents.CONTROL_TOUCH.id, cvh.deviceType, cvh.uid, isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun drag(cvh: ControlViewHolder, isLocked: Boolean) {
|
||||||
|
log(ControlsEvents.CONTROL_DRAG.id, cvh.deviceType, cvh.uid, isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun longPress(cvh: ControlViewHolder, isLocked: Boolean) {
|
||||||
|
log(ControlsEvents.CONTROL_LONG_PRESS.id, cvh.deviceType, cvh.uid, isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshBegin(uid: Int, isLocked: Boolean) {
|
||||||
|
assignInstanceId()
|
||||||
|
log(ControlsEvents.CONTROL_REFRESH_BEGIN.id, 0, uid, isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshEnd(cvh: ControlViewHolder, isLocked: Boolean) {
|
||||||
|
log(ControlsEvents.CONTROL_REFRESH_END.id, cvh.deviceType, cvh.uid, isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a controls-related event
|
||||||
|
*
|
||||||
|
* @param eventId Main UIEvent to capture
|
||||||
|
* @param deviceType One of {@link android.service.controls.DeviceTypes}
|
||||||
|
* @param packageName Package name of the service that receives the request
|
||||||
|
* @param isLocked Is the device locked at the start of the action?
|
||||||
|
*/
|
||||||
|
fun log(
|
||||||
|
eventId: Int,
|
||||||
|
@DeviceType deviceType: Int,
|
||||||
|
uid: Int,
|
||||||
|
isLocked: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
private enum class ControlsEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
|
||||||
|
@UiEvent(doc = "User touched a control")
|
||||||
|
CONTROL_TOUCH(714),
|
||||||
|
|
||||||
|
@UiEvent(doc = "User dragged a control")
|
||||||
|
CONTROL_DRAG(713),
|
||||||
|
|
||||||
|
@UiEvent(doc = "User long-pressed a control")
|
||||||
|
CONTROL_LONG_PRESS(715),
|
||||||
|
|
||||||
|
@UiEvent(doc = "User has opened controls, and a state refresh has begun")
|
||||||
|
CONTROL_REFRESH_BEGIN(716),
|
||||||
|
|
||||||
|
@UiEvent(doc = "User has opened controls, and a state refresh has ended")
|
||||||
|
CONTROL_REFRESH_END(717);
|
||||||
|
|
||||||
|
override fun getId() = metricId
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.controls
|
||||||
|
|
||||||
|
import android.service.controls.DeviceTypes.DeviceType
|
||||||
|
|
||||||
|
import com.android.internal.logging.InstanceIdSequence
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.shared.system.SysUiStatsLog
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation for logging UI events related to controls
|
||||||
|
*/
|
||||||
|
@SysUISingleton
|
||||||
|
class ControlsMetricsLoggerImpl @Inject constructor() : ControlsMetricsLogger {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val INSTANCE_ID_MAX = 1 shl 13
|
||||||
|
}
|
||||||
|
|
||||||
|
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
|
||||||
|
private var instanceId = 0
|
||||||
|
|
||||||
|
override fun assignInstanceId() {
|
||||||
|
instanceId = instanceIdSequence.newInstanceId().id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@see ControlsMetricsLogger#log}
|
||||||
|
*/
|
||||||
|
override fun log(
|
||||||
|
eventId: Int,
|
||||||
|
@DeviceType deviceType: Int,
|
||||||
|
uid: Int,
|
||||||
|
isLocked: Boolean
|
||||||
|
) {
|
||||||
|
SysUiStatsLog.write(
|
||||||
|
SysUiStatsLog.DEVICE_CONTROL_CHANGED,
|
||||||
|
eventId,
|
||||||
|
instanceId,
|
||||||
|
deviceType,
|
||||||
|
uid,
|
||||||
|
isLocked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ package com.android.systemui.controls.dagger
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLoggerImpl
|
||||||
import com.android.systemui.controls.controller.ControlsBindingController
|
import com.android.systemui.controls.controller.ControlsBindingController
|
||||||
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
|
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
@@ -79,6 +81,9 @@ abstract class ControlsModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
|
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideControlActionCoordinator(
|
abstract fun provideControlActionCoordinator(
|
||||||
coordinator: ControlActionCoordinatorImpl
|
coordinator: ControlActionCoordinatorImpl
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.android.systemui.controls.ui
|
|||||||
|
|
||||||
import android.annotation.MainThread
|
import android.annotation.MainThread
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@@ -33,6 +32,7 @@ import android.util.Log
|
|||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.globalactions.GlobalActionsComponent
|
import com.android.systemui.globalactions.GlobalActionsComponent
|
||||||
@@ -54,13 +54,15 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
private val globalActionsComponent: GlobalActionsComponent,
|
private val globalActionsComponent: GlobalActionsComponent,
|
||||||
private val taskViewFactory: Optional<TaskViewFactory>,
|
private val taskViewFactory: Optional<TaskViewFactory>,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val lazyUiController: Lazy<ControlsUiController>
|
private val lazyUiController: Lazy<ControlsUiController>,
|
||||||
|
private val controlsMetricsLogger: ControlsMetricsLogger
|
||||||
) : ControlActionCoordinator {
|
) : ControlActionCoordinator {
|
||||||
private var dialog: Dialog? = null
|
private var dialog: Dialog? = null
|
||||||
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
private var pendingAction: Action? = null
|
private var pendingAction: Action? = null
|
||||||
private var actionsInProgress = mutableSetOf<String>()
|
private var actionsInProgress = mutableSetOf<String>()
|
||||||
|
private val isLocked: Boolean
|
||||||
|
get() = !keyguardStateController.isUnlocked()
|
||||||
override var activityContext: Context? = null
|
override var activityContext: Context? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -73,6 +75,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
|
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
|
||||||
|
controlsMetricsLogger.touch(cvh, isLocked)
|
||||||
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
||||||
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
||||||
cvh.action(BooleanAction(templateId, !isChecked))
|
cvh.action(BooleanAction(templateId, !isChecked))
|
||||||
@@ -80,6 +83,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
|
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
|
||||||
|
controlsMetricsLogger.touch(cvh, isLocked)
|
||||||
val blockable = cvh.usePanel()
|
val blockable = cvh.usePanel()
|
||||||
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
||||||
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
||||||
@@ -100,12 +104,14 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
|
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
|
||||||
|
controlsMetricsLogger.drag(cvh, isLocked)
|
||||||
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
||||||
cvh.action(FloatAction(templateId, newValue))
|
cvh.action(FloatAction(templateId, newValue))
|
||||||
}, false /* blockable */))
|
}, false /* blockable */))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun longPress(cvh: ControlViewHolder) {
|
override fun longPress(cvh: ControlViewHolder) {
|
||||||
|
controlsMetricsLogger.longPress(cvh, isLocked)
|
||||||
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
||||||
// Long press snould only be called when there is valid control state, otherwise ignore
|
// Long press snould only be called when there is valid control state, otherwise ignore
|
||||||
cvh.cws.control?.let {
|
cvh.cws.control?.let {
|
||||||
@@ -116,7 +122,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun runPendingAction(controlId: String) {
|
override fun runPendingAction(controlId: String) {
|
||||||
if (!keyguardStateController.isUnlocked()) return
|
if (isLocked) return
|
||||||
if (pendingAction?.controlId == controlId) {
|
if (pendingAction?.controlId == controlId) {
|
||||||
pendingAction?.invoke()
|
pendingAction?.invoke()
|
||||||
pendingAction = null
|
pendingAction = null
|
||||||
@@ -141,28 +147,17 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun bouncerOrRun(action: Action) {
|
fun bouncerOrRun(action: Action) {
|
||||||
if (keyguardStateController.isShowing()) {
|
if (keyguardStateController.isShowing()) {
|
||||||
var closeDialog = !keyguardStateController.isUnlocked()
|
if (isLocked) {
|
||||||
if (closeDialog) {
|
|
||||||
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
|
|
||||||
// pending actions will only run after the control state has been refreshed
|
// pending actions will only run after the control state has been refreshed
|
||||||
pendingAction = action
|
pendingAction = action
|
||||||
}
|
}
|
||||||
|
val wasLocked = isLocked
|
||||||
activityStarter.dismissKeyguardThenExecute({
|
activityStarter.dismissKeyguardThenExecute({
|
||||||
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
|
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
|
||||||
if (closeDialog) {
|
if (wasLocked && activityContext == null) {
|
||||||
activityContext?.let {
|
globalActionsComponent.handleShowGlobalActionsMenu()
|
||||||
val i = Intent().apply {
|
|
||||||
component = ComponentName(context, ControlsActivity::class.java)
|
|
||||||
addFlags(
|
|
||||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
|
|
||||||
}
|
|
||||||
it.startActivity(i)
|
|
||||||
} ?: run {
|
|
||||||
globalActionsComponent.handleShowGlobalActionsMenu()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
action.invoke()
|
action.invoke()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import android.widget.TextView
|
|||||||
import com.android.internal.graphics.ColorUtils
|
import com.android.internal.graphics.ColorUtils
|
||||||
import com.android.systemui.Interpolators
|
import com.android.systemui.Interpolators
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -63,7 +64,9 @@ class ControlViewHolder(
|
|||||||
val controlsController: ControlsController,
|
val controlsController: ControlsController,
|
||||||
val uiExecutor: DelayableExecutor,
|
val uiExecutor: DelayableExecutor,
|
||||||
val bgExecutor: DelayableExecutor,
|
val bgExecutor: DelayableExecutor,
|
||||||
val controlActionCoordinator: ControlActionCoordinator
|
val controlActionCoordinator: ControlActionCoordinator,
|
||||||
|
val controlsMetricsLogger: ControlsMetricsLogger,
|
||||||
|
val uid: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -141,7 +144,7 @@ class ControlViewHolder(
|
|||||||
status.setSelected(true)
|
status.setSelected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bindData(cws: ControlWithState) {
|
fun bindData(cws: ControlWithState, isLocked: Boolean) {
|
||||||
// If an interaction is in progress, the update may visually interfere with the action the
|
// If an interaction is in progress, the update may visually interfere with the action the
|
||||||
// action the user wants to make. Don't apply the update, and instead assume a new update
|
// action the user wants to make. Don't apply the update, and instead assume a new update
|
||||||
// will coming from when the user interaction is complete.
|
// will coming from when the user interaction is complete.
|
||||||
@@ -171,10 +174,16 @@ class ControlViewHolder(
|
|||||||
controlActionCoordinator.runPendingAction(cws.ci.controlId)
|
controlActionCoordinator.runPendingAction(cws.ci.controlId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val wasLoading = isLoading
|
||||||
isLoading = false
|
isLoading = false
|
||||||
behavior = bindBehavior(behavior,
|
behavior = bindBehavior(behavior,
|
||||||
findBehaviorClass(controlStatus, controlTemplate, deviceType))
|
findBehaviorClass(controlStatus, controlTemplate, deviceType))
|
||||||
updateContentDescription()
|
updateContentDescription()
|
||||||
|
|
||||||
|
// Only log one event per control, at the moment we have determined that the control
|
||||||
|
// switched from the loading to done state
|
||||||
|
val doneLoading = wasLoading && !isLoading
|
||||||
|
if (doneLoading) controlsMetricsLogger.refreshEnd(this, isLocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun actionResponse(@ControlAction.ResponseResult response: Int) {
|
fun actionResponse(@ControlAction.ResponseResult response: Int) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import android.widget.ListPopupWindow
|
|||||||
import android.widget.Space
|
import android.widget.Space
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.CustomIconCache
|
import com.android.systemui.controls.CustomIconCache
|
||||||
import com.android.systemui.controls.controller.ControlInfo
|
import com.android.systemui.controls.controller.ControlInfo
|
||||||
@@ -58,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Main
|
|||||||
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.statusbar.phone.ShadeController
|
import com.android.systemui.statusbar.phone.ShadeController
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
@@ -77,7 +79,9 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
val controlActionCoordinator: ControlActionCoordinator,
|
val controlActionCoordinator: ControlActionCoordinator,
|
||||||
private val activityStarter: ActivityStarter,
|
private val activityStarter: ActivityStarter,
|
||||||
private val shadeController: ShadeController,
|
private val shadeController: ShadeController,
|
||||||
private val iconCache: CustomIconCache
|
private val iconCache: CustomIconCache,
|
||||||
|
private val controlsMetricsLogger: ControlsMetricsLogger,
|
||||||
|
private val keyguardStateController: KeyguardStateController
|
||||||
) : ControlsUiController {
|
) : ControlsUiController {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -133,7 +137,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
return object : ControlsListingController.ControlsListingCallback {
|
return object : ControlsListingController.ControlsListingCallback {
|
||||||
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
||||||
val lastItems = serviceInfos.map {
|
val lastItems = serviceInfos.map {
|
||||||
SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName)
|
val uid = it.serviceInfo.applicationInfo.uid
|
||||||
|
SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
|
||||||
}
|
}
|
||||||
uiExecutor.execute {
|
uiExecutor.execute {
|
||||||
parent.removeAllViews()
|
parent.removeAllViews()
|
||||||
@@ -282,8 +287,19 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private fun showControlsView(items: List<SelectionItem>) {
|
private fun showControlsView(items: List<SelectionItem>) {
|
||||||
controlViewsById.clear()
|
controlViewsById.clear()
|
||||||
|
|
||||||
createListView()
|
val itemsByComponent = items.associateBy { it.componentName }
|
||||||
createDropDown(items)
|
val itemsWithStructure = mutableListOf<SelectionItem>()
|
||||||
|
allStructures.mapNotNullTo(itemsWithStructure) {
|
||||||
|
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
|
||||||
|
}
|
||||||
|
itemsWithStructure.sortWith(localeComparator)
|
||||||
|
|
||||||
|
val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
|
||||||
|
|
||||||
|
controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
|
||||||
|
|
||||||
|
createListView(selectionItem)
|
||||||
|
createDropDown(itemsWithStructure, selectionItem)
|
||||||
createMenu()
|
createMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,22 +341,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDropDown(items: List<SelectionItem>) {
|
private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
|
||||||
items.forEach {
|
items.forEach {
|
||||||
RenderInfo.registerComponentIcon(it.componentName, it.icon)
|
RenderInfo.registerComponentIcon(it.componentName, it.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
val itemsByComponent = items.associateBy { it.componentName }
|
|
||||||
val itemsWithStructure = mutableListOf<SelectionItem>()
|
|
||||||
allStructures.mapNotNullTo(itemsWithStructure) {
|
|
||||||
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
|
|
||||||
}
|
|
||||||
itemsWithStructure.sortWith(localeComparator)
|
|
||||||
|
|
||||||
val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
|
|
||||||
|
|
||||||
var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
|
var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
|
||||||
addAll(itemsWithStructure)
|
addAll(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -349,13 +356,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
* a similar effect
|
* a similar effect
|
||||||
*/
|
*/
|
||||||
val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply {
|
val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply {
|
||||||
setText(selectionItem.getTitle())
|
setText(selected.getTitle())
|
||||||
// override the default color on the dropdown drawable
|
// override the default color on the dropdown drawable
|
||||||
(getBackground() as LayerDrawable).getDrawable(0)
|
(getBackground() as LayerDrawable).getDrawable(0)
|
||||||
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
|
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemsWithStructure.size == 1) {
|
if (items.size == 1) {
|
||||||
spinner.setBackground(null)
|
spinner.setBackground(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -388,7 +395,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createListView() {
|
private fun createListView(selected: SelectionItem) {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
inflater.inflate(R.layout.controls_with_favorites, parent, true)
|
inflater.inflate(R.layout.controls_with_favorites, parent, true)
|
||||||
|
|
||||||
@@ -421,9 +428,11 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
controlsController.get(),
|
controlsController.get(),
|
||||||
uiExecutor,
|
uiExecutor,
|
||||||
bgExecutor,
|
bgExecutor,
|
||||||
controlActionCoordinator
|
controlActionCoordinator,
|
||||||
|
controlsMetricsLogger,
|
||||||
|
selected.uid
|
||||||
)
|
)
|
||||||
cvh.bindData(it)
|
cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
|
||||||
controlViewsById.put(key, cvh)
|
controlViewsById.put(key, cvh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,6 +537,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
|
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
|
||||||
|
val isLocked = !keyguardStateController.isUnlocked()
|
||||||
controls.forEach { c ->
|
controls.forEach { c ->
|
||||||
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
|
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
|
||||||
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
|
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
|
||||||
@@ -536,8 +546,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
val key = ControlKey(componentName, c.getControlId())
|
val key = ControlKey(componentName, c.getControlId())
|
||||||
controlsById.put(key, cws)
|
controlsById.put(key, cws)
|
||||||
|
|
||||||
uiExecutor.execute {
|
controlViewsById.get(key)?.let {
|
||||||
controlViewsById.get(key)?.bindData(cws)
|
uiExecutor.execute { it.bindData(cws, isLocked) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,7 +576,8 @@ private data class SelectionItem(
|
|||||||
val appName: CharSequence,
|
val appName: CharSequence,
|
||||||
val structure: CharSequence,
|
val structure: CharSequence,
|
||||||
val icon: Drawable,
|
val icon: Drawable,
|
||||||
val componentName: ComponentName
|
val componentName: ComponentName,
|
||||||
|
val uid: Int
|
||||||
) {
|
) {
|
||||||
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
|
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.android.systemui.controls.ui
|
|||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.globalactions.GlobalActionsComponent
|
import com.android.systemui.globalactions.GlobalActionsComponent
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
@@ -63,6 +64,8 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
|
|||||||
private lateinit var taskViewFactory: Optional<TaskViewFactory>
|
private lateinit var taskViewFactory: Optional<TaskViewFactory>
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private lateinit var cvh: ControlViewHolder
|
private lateinit var cvh: ControlViewHolder
|
||||||
|
@Mock
|
||||||
|
private lateinit var metricsLogger: ControlsMetricsLogger
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> any(): T = Mockito.any<T>()
|
fun <T> any(): T = Mockito.any<T>()
|
||||||
@@ -86,7 +89,8 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
|
|||||||
globalActionsComponent,
|
globalActionsComponent,
|
||||||
taskViewFactory,
|
taskViewFactory,
|
||||||
getFakeBroadcastDispatcher(),
|
getFakeBroadcastDispatcher(),
|
||||||
lazyUiController
|
lazyUiController,
|
||||||
|
metricsLogger
|
||||||
))
|
))
|
||||||
|
|
||||||
`when`(cvh.cws.ci.controlId).thenReturn(ID)
|
`when`(cvh.cws.ci.controlId).thenReturn(ID)
|
||||||
|
|||||||
Reference in New Issue
Block a user