Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 31bc8d0e authored by Matt Pietal's avatar Matt Pietal Committed by Automerger Merge Worker
Browse files

Merge "Controls UI - Lock screen support" into rvc-dev am: 303e4056 am: 0d6dc417

Change-Id: I0e238e65305fbbc972194e8b771c1bd01aca0550
parents 5594cdb3 0d6dc417
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.ControlsUiControllerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
@@ -55,6 +57,11 @@ abstract class ControlsModule {
    @Binds
    abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController

    @Binds
    abstract fun provideControlActionCoordinator(
        coordinator: ControlActionCoordinatorImpl
    ): ControlActionCoordinator

    @BindsOptionalOf
    abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper

+34 −71
Original line number Diff line number Diff line
@@ -16,89 +16,52 @@

package com.android.systemui.controls.ui

import android.app.Dialog
import android.content.Intent
import android.os.Vibrator
import android.os.VibrationEffect
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
import android.view.HapticFeedbackConstants
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor

object ControlActionCoordinator {
    const val MIN_LEVEL = 0
    const val MAX_LEVEL = 10000

    private var dialog: Dialog? = null
    private var vibrator: Vibrator? = null

    lateinit var bgExecutor: DelayableExecutor

    fun closeDialog() {
        dialog?.dismiss()
        dialog = null
    }
/**
 * All control interactions should be routed through this coordinator. It handles dispatching of
 * actions, haptic support, and all detail panels
 */
interface ControlActionCoordinator {

    /**
     * Create custom vibrations, all intended to create very subtle feedback while interacting
     * with the controls.
     * Close any dialogs which may have been open
     */
    fun initialize(vibrator: Vibrator, bgExecutor: DelayableExecutor) {
        this.vibrator = vibrator
        this.bgExecutor = bgExecutor
    }
    fun closeDialogs()

    fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
        val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
        vibrate(effect)
        cvh.action(BooleanAction(templateId, !isChecked))
    }
    /**
     * Create a [BooleanAction], and inform the service of a request to change the device state
     *
     * @param cvh [ControlViewHolder] for the control
     * @param templateId id of the control's template, as given by the service
     * @param isChecked new requested state of the control
     */
    fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean)

    fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
        vibrate(Vibrations.toggleOnEffect)
        if (cvh.usePanel()) {
            showDialog(cvh, control.getAppIntent().getIntent())
        } else {
            cvh.action(CommandAction(templateId))
        }
    }
    /**
     * For non-toggle controls, touching may create a dialog or invoke a [CommandAction].
     *
     * @param cvh [ControlViewHolder] for the control
     * @param templateId id of the control's template, as given by the service
     * @param control the control as sent by the service
     */
    fun touch(cvh: ControlViewHolder, templateId: String, control: Control)

    fun drag(isEdge: Boolean) {
        if (isEdge) {
            vibrate(Vibrations.rangeEdgeEffect)
        } else {
            vibrate(Vibrations.rangeMiddleEffect)
        }
    }
    /**
     * When a ToggleRange control is interacting with, a drag event is sent.
     *
     * @param isEdge did the drag event reach a control edge
     */
    fun drag(isEdge: Boolean)

    /**
     * All long presses will be shown in a 3/4 height bottomsheet panel, in order for the user to
     * retain context with their favorited controls in the power menu.
     */
    fun longPress(cvh: ControlViewHolder) {
        // Long press snould only be called when there is valid control state, otherwise ignore
        cvh.cws.control?.let {
            cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
            showDialog(cvh, it.getAppIntent().getIntent())
        }
    }

    private fun vibrate(effect: VibrationEffect) {
        vibrator?.let {
            bgExecutor.execute { it.vibrate(effect) }
        }
    }

    private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
        dialog = DetailDialog(cvh, intent).also {
            it.setOnDismissListener { _ -> dialog = null }
            it.show()
        }
    }
    fun longPress(cvh: ControlViewHolder)

    fun setFocusedElement(cvh: ControlViewHolder?, controlsController: ControlsController) {
        controlsController.onFocusChanged(cvh?.cws)
    }
    /**
     * Event to inform the UI that the user has has focused on a single control.
     */
    fun setFocusedElement(cvh: ControlViewHolder?)
}
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.ui

import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Vibrator
import android.os.VibrationEffect
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor

import dagger.Lazy

import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ControlActionCoordinatorImpl @Inject constructor(
    private val context: Context,
    private val bgExecutor: DelayableExecutor,
    private val controlsController: Lazy<ControlsController>,
    private val activityStarter: ActivityStarter,
    private val keyguardStateController: KeyguardStateController,
    private val globalActionsComponent: GlobalActionsComponent
) : ControlActionCoordinator {
    private var dialog: Dialog? = null
    private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    private var lastAction: (() -> Unit)? = null

    override fun closeDialogs() {
        dialog?.dismiss()
        dialog = null
    }

    override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
        bouncerOrRun {
            val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
            vibrate(effect)
            cvh.action(BooleanAction(templateId, !isChecked))
        }
    }

    override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
        vibrate(Vibrations.toggleOnEffect)

        bouncerOrRun {
            if (cvh.usePanel()) {
                showDialog(cvh, control.getAppIntent().getIntent())
            } else {
                cvh.action(CommandAction(templateId))
            }
        }
    }

    override fun drag(isEdge: Boolean) {
        bouncerOrRun {
            if (isEdge) {
                vibrate(Vibrations.rangeEdgeEffect)
            } else {
                vibrate(Vibrations.rangeMiddleEffect)
            }
        }
    }

    override fun longPress(cvh: ControlViewHolder) {
        bouncerOrRun {
            // Long press snould only be called when there is valid control state, otherwise ignore
            cvh.cws.control?.let {
                cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
                showDialog(cvh, it.getAppIntent().getIntent())
            }
        }
    }

    override fun setFocusedElement(cvh: ControlViewHolder?) {
        controlsController.get().onFocusChanged(cvh?.cws)
    }

    private fun bouncerOrRun(f: () -> Unit) {
        if (!keyguardStateController.isUnlocked()) {
            context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
            activityStarter.dismissKeyguardThenExecute({
                Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
                globalActionsComponent.handleShowGlobalActionsMenu()
                f()
                true
            }, null, true)
        } else {
            f()
        }
    }

    private fun vibrate(effect: VibrationEffect) {
        bgExecutor.execute { vibrator.vibrate(effect) }
    }

    private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
        dialog = DetailDialog(cvh, intent).also {
            it.setOnDismissListener { _ -> dialog = null }
            it.show()
        }
    }
}
+6 −2
Original line number Diff line number Diff line
@@ -53,7 +53,8 @@ class ControlViewHolder(
    val layout: ViewGroup,
    val controlsController: ControlsController,
    val uiExecutor: DelayableExecutor,
    val bgExecutor: DelayableExecutor
    val bgExecutor: DelayableExecutor,
    val controlActionCoordinator: ControlActionCoordinator
) {

    companion object {
@@ -65,6 +66,9 @@ class ControlViewHolder(
            DeviceTypes.TYPE_THERMOSTAT,
            DeviceTypes.TYPE_CAMERA
        )

        const val MIN_LEVEL = 0
        const val MAX_LEVEL = 10000
    }

    private val toggleBackgroundIntensity: Float = layout.context.resources
@@ -121,7 +125,7 @@ class ControlViewHolder(
        cws.control?.let {
            layout.setClickable(true)
            layout.setOnLongClickListener(View.OnLongClickListener() {
                ControlActionCoordinator.longPress(this@ControlViewHolder)
                controlActionCoordinator.longPress(this@ControlViewHolder)
                true
            })
        }
+18 −12
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Process
import android.os.Vibrator
import android.service.controls.Control
import android.util.Log
import android.util.TypedValue
@@ -59,6 +58,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.text.Collator
@@ -75,7 +76,10 @@ class ControlsUiControllerImpl @Inject constructor (
    @Main val uiExecutor: DelayableExecutor,
    @Background val bgExecutor: DelayableExecutor,
    val controlsListingController: Lazy<ControlsListingController>,
    @Main val sharedPreferences: SharedPreferences
    @Main val sharedPreferences: SharedPreferences,
    val controlActionCoordinator: ControlActionCoordinator,
    private val activityStarter: ActivityStarter,
    private val keyguardStateController: KeyguardStateController
) : ControlsUiController {

    companion object {
@@ -107,11 +111,6 @@ class ControlsUiControllerImpl @Inject constructor (

    private lateinit var listingCallback: ControlsListingController.ControlsListingCallback

    init {
        val vibratorService = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        ControlActionCoordinator.initialize(vibratorService, bgExecutor)
    }

    private fun createCallback(
        onResult: (List<SelectionItem>) -> Unit
    ): ControlsListingController.ControlsListingCallback {
@@ -267,8 +266,16 @@ class ControlsUiControllerImpl @Inject constructor (
    private fun startActivity(context: Context, intent: Intent) {
        // Force animations when transitioning from a dialog to an activity
        intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
        context.startActivity(intent)
        dismissGlobalActions.run()

        if (!keyguardStateController.isUnlocked()) {
            activityStarter.dismissKeyguardThenExecute({
                context.startActivity(intent)
                true
            }, null, true)
        } else {
            context.startActivity(intent)
        }
    }

    private fun showControlsView(items: List<SelectionItem>) {
@@ -463,7 +470,8 @@ class ControlsUiControllerImpl @Inject constructor (
                    baseLayout,
                    controlsController.get(),
                    uiExecutor,
                    bgExecutor
                    bgExecutor,
                    controlActionCoordinator
                )
                cvh.bindData(it)
                controlViewsById.put(key, cvh)
@@ -545,9 +553,7 @@ class ControlsUiControllerImpl @Inject constructor (
        controlViewsById.forEach {
            it.value.dismiss()
        }

        ControlActionCoordinator.closeDialog()

        controlActionCoordinator.closeDialogs()
        controlsController.get().unsubscribe()

        parent.removeAllViews()
Loading