Loading packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * 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 } } packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * 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 ) } } packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.systemui.controls.dagger import android.app.Activity import android.content.pm.PackageManager import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsMetricsLoggerImpl import com.android.systemui.controls.controller.ControlsBindingController import com.android.systemui.controls.controller.ControlsBindingControllerImpl import com.android.systemui.controls.controller.ControlsController Loading Loading @@ -79,6 +81,9 @@ abstract class ControlsModule { @Binds abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController @Binds abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger @Binds abstract fun provideControlActionCoordinator( coordinator: ControlActionCoordinatorImpl Loading packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +14 −19 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager Loading @@ -33,6 +32,7 @@ import android.util.Log import android.view.HapticFeedbackConstants import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent Loading @@ -54,13 +54,15 @@ class ControlActionCoordinatorImpl @Inject constructor( private val globalActionsComponent: GlobalActionsComponent, private val taskViewFactory: Optional<TaskViewFactory>, private val broadcastDispatcher: BroadcastDispatcher, private val lazyUiController: Lazy<ControlsUiController> private val lazyUiController: Lazy<ControlsUiController>, private val controlsMetricsLogger: ControlsMetricsLogger ) : ControlActionCoordinator { private var dialog: Dialog? = null private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() private val isLocked: Boolean get() = !keyguardStateController.isUnlocked() override var activityContext: Context? = null companion object { Loading @@ -73,6 +75,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { controlsMetricsLogger.touch(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) cvh.action(BooleanAction(templateId, !isChecked)) Loading @@ -80,6 +83,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { controlsMetricsLogger.touch(cvh, isLocked) val blockable = cvh.usePanel() bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) Loading @@ -100,12 +104,14 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) { controlsMetricsLogger.drag(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.action(FloatAction(templateId, newValue)) }, false /* blockable */)) } override fun longPress(cvh: ControlViewHolder) { controlsMetricsLogger.longPress(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { Loading @@ -116,7 +122,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun runPendingAction(controlId: String) { if (!keyguardStateController.isUnlocked()) return if (isLocked) return if (pendingAction?.controlId == controlId) { pendingAction?.invoke() pendingAction = null Loading @@ -141,28 +147,17 @@ class ControlActionCoordinatorImpl @Inject constructor( @VisibleForTesting fun bouncerOrRun(action: Action) { if (keyguardStateController.isShowing()) { var closeDialog = !keyguardStateController.isUnlocked() if (closeDialog) { if (isLocked) { context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) // 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 (closeDialog) { activityContext?.let { 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 { if (wasLocked && activityContext == null) { globalActionsComponent.handleShowGlobalActionsMenu() } } else { action.invoke() } Loading packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +11 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import android.widget.TextView import com.android.internal.graphics.ColorUtils import com.android.systemui.Interpolators import com.android.systemui.R import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.concurrency.DelayableExecutor import kotlin.reflect.KClass Loading @@ -63,7 +64,9 @@ class ControlViewHolder( val controlsController: ControlsController, val uiExecutor: DelayableExecutor, val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, val uid: Int ) { companion object { Loading Loading @@ -141,7 +144,7 @@ class ControlViewHolder( 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 // 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. Loading Loading @@ -171,10 +174,16 @@ class ControlViewHolder( controlActionCoordinator.runPendingAction(cws.ci.controlId) } val wasLoading = isLoading isLoading = false behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, controlTemplate, deviceType)) 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) { Loading Loading
packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * 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 } }
packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * 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 ) } }
packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.systemui.controls.dagger import android.app.Activity import android.content.pm.PackageManager import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsMetricsLoggerImpl import com.android.systemui.controls.controller.ControlsBindingController import com.android.systemui.controls.controller.ControlsBindingControllerImpl import com.android.systemui.controls.controller.ControlsController Loading Loading @@ -79,6 +81,9 @@ abstract class ControlsModule { @Binds abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController @Binds abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger @Binds abstract fun provideControlActionCoordinator( coordinator: ControlActionCoordinatorImpl Loading
packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +14 −19 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager Loading @@ -33,6 +32,7 @@ import android.util.Log import android.view.HapticFeedbackConstants import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent Loading @@ -54,13 +54,15 @@ class ControlActionCoordinatorImpl @Inject constructor( private val globalActionsComponent: GlobalActionsComponent, private val taskViewFactory: Optional<TaskViewFactory>, private val broadcastDispatcher: BroadcastDispatcher, private val lazyUiController: Lazy<ControlsUiController> private val lazyUiController: Lazy<ControlsUiController>, private val controlsMetricsLogger: ControlsMetricsLogger ) : ControlActionCoordinator { private var dialog: Dialog? = null private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() private val isLocked: Boolean get() = !keyguardStateController.isUnlocked() override var activityContext: Context? = null companion object { Loading @@ -73,6 +75,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { controlsMetricsLogger.touch(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) cvh.action(BooleanAction(templateId, !isChecked)) Loading @@ -80,6 +83,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { controlsMetricsLogger.touch(cvh, isLocked) val blockable = cvh.usePanel() bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) Loading @@ -100,12 +104,14 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) { controlsMetricsLogger.drag(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.action(FloatAction(templateId, newValue)) }, false /* blockable */)) } override fun longPress(cvh: ControlViewHolder) { controlsMetricsLogger.longPress(cvh, isLocked) bouncerOrRun(createAction(cvh.cws.ci.controlId, { // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { Loading @@ -116,7 +122,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun runPendingAction(controlId: String) { if (!keyguardStateController.isUnlocked()) return if (isLocked) return if (pendingAction?.controlId == controlId) { pendingAction?.invoke() pendingAction = null Loading @@ -141,28 +147,17 @@ class ControlActionCoordinatorImpl @Inject constructor( @VisibleForTesting fun bouncerOrRun(action: Action) { if (keyguardStateController.isShowing()) { var closeDialog = !keyguardStateController.isUnlocked() if (closeDialog) { if (isLocked) { context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) // 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 (closeDialog) { activityContext?.let { 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 { if (wasLocked && activityContext == null) { globalActionsComponent.handleShowGlobalActionsMenu() } } else { action.invoke() } Loading
packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +11 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import android.widget.TextView import com.android.internal.graphics.ColorUtils import com.android.systemui.Interpolators import com.android.systemui.R import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.concurrency.DelayableExecutor import kotlin.reflect.KClass Loading @@ -63,7 +64,9 @@ class ControlViewHolder( val controlsController: ControlsController, val uiExecutor: DelayableExecutor, val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, val uid: Int ) { companion object { Loading Loading @@ -141,7 +144,7 @@ class ControlViewHolder( 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 // 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. Loading Loading @@ -171,10 +174,16 @@ class ControlViewHolder( controlActionCoordinator.runPendingAction(cws.ci.controlId) } val wasLoading = isLoading isLoading = false behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, controlTemplate, deviceType)) 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) { Loading