diff --git a/core/java/android/service/controls/TokenProvider.aidl b/core/java/android/service/controls/TokenProvider.aidl new file mode 100644 index 0000000000000..8f4b7953f6598 --- /dev/null +++ b/core/java/android/service/controls/TokenProvider.aidl @@ -0,0 +1,7 @@ +package android.service.controls; + +/** @hide */ +interface TokenProvider { + void setAuthToken(String token); + String getAccountName(); +} \ No newline at end of file diff --git a/packages/SystemUI/res/color/light_background.xml b/packages/SystemUI/res/color/light_background.xml new file mode 100644 index 0000000000000..2effd991f666c --- /dev/null +++ b/packages/SystemUI/res/color/light_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/color/light_foreground.xml b/packages/SystemUI/res/color/light_foreground.xml new file mode 100644 index 0000000000000..8143028795dfc --- /dev/null +++ b/packages/SystemUI/res/color/light_foreground.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/color/lock_background.xml b/packages/SystemUI/res/color/lock_background.xml new file mode 100644 index 0000000000000..646fe5dfe712d --- /dev/null +++ b/packages/SystemUI/res/color/lock_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/color/lock_foreground.xml b/packages/SystemUI/res/color/lock_foreground.xml new file mode 100644 index 0000000000000..3e05653bce928 --- /dev/null +++ b/packages/SystemUI/res/color/lock_foreground.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/color/unknown_foreground.xml b/packages/SystemUI/res/color/unknown_foreground.xml new file mode 100644 index 0000000000000..bf028f18a7def --- /dev/null +++ b/packages/SystemUI/res/color/unknown_foreground.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/drawable/control_background.xml b/packages/SystemUI/res/drawable/control_background.xml new file mode 100644 index 0000000000000..b246ea0a39352 --- /dev/null +++ b/packages/SystemUI/res/drawable/control_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/drawable/control_layer.xml b/packages/SystemUI/res/drawable/control_layer.xml new file mode 100644 index 0000000000000..fe8c4a4af1bb7 --- /dev/null +++ b/packages/SystemUI/res/drawable/control_layer.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml new file mode 100644 index 0000000000000..1e282ad0eec78 --- /dev/null +++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml new file mode 100644 index 0000000000000..45a658fe07da6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml new file mode 100644 index 0000000000000..78c3cc54f022b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml new file mode 100644 index 0000000000000..f4299e6cda111 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml new file mode 100644 index 0000000000000..59fe0a9146562 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml @@ -0,0 +1,12 @@ + + + + diff --git a/packages/SystemUI/res/drawable/ic_more_vert.xml b/packages/SystemUI/res/drawable/ic_more_vert.xml new file mode 100644 index 0000000000000..1309fa875b558 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_more_vert.xml @@ -0,0 +1,24 @@ + + + + diff --git a/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml new file mode 100644 index 0000000000000..cd957196e4dc3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml new file mode 100644 index 0000000000000..3eb7dd637abef --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml new file mode 100644 index 0000000000000..f4edd875ddff5 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml new file mode 100644 index 0000000000000..bb535ceaed8cd --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml new file mode 100644 index 0000000000000..86b9591238f1d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml new file mode 100644 index 0000000000000..687c9c417fa62 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml new file mode 100644 index 0000000000000..3c4c61e30bc12 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml new file mode 100644 index 0000000000000..79672caed61b1 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_no_favorites.xml @@ -0,0 +1,18 @@ + + + diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml new file mode 100644 index 0000000000000..13a6b36accd35 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_row.xml @@ -0,0 +1,22 @@ + + + diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml new file mode 100644 index 0000000000000..7804fe6b6272b --- /dev/null +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index 4cfb47e3c6421..674148495478c 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -111,20 +111,7 @@ app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/global_actions_panel"> - - + diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 8dd2a8b9d07a2..09058f2460cb1 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -200,18 +200,28 @@ #3C4043 #202124 + #FCE8E6 #F28B82 #B71C1C #C5221F + #E8F0FE #AECBFA #8AB4F8 + #FF4285F4 #1A73E8 #1967D2 + #FEF7E0 #FFFBBC04 + #FF34A853 - #FF4285F4 + + #B06000 #FF9900 + + + ?android:attr/textColorPrimary + ?android:attr/colorBackgroundFloating diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b40c5c0b02641..cc58b2014496a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1181,5 +1181,12 @@ 45dp 40dp + + 5dp + 15dp + 100dp + 15dp + 12dp + 18dp 32dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 44a7fda6bce3d..1f13f8dc02fea 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2546,4 +2546,7 @@ Magnification Window Magnification Window Controls + + + Quick Controls diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt new file mode 100644 index 0000000000000..81b5f36985670 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -0,0 +1,400 @@ +/* + * 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.content.Context +import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon +import android.graphics.drawable.LayerDrawable +import android.service.controls.Control +import android.service.controls.DeviceTypes +import android.service.controls.actions.BooleanAction +import android.service.controls.actions.ControlAction +import android.service.controls.actions.FloatAction +import android.service.controls.templates.ControlTemplate +import android.service.controls.templates.RangeTemplate +import android.service.controls.templates.ToggleRangeTemplate +import android.service.controls.templates.ToggleTemplate +import android.util.TypedValue +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView + +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.R + +private const val MIN_LEVEL = 0 +private const val MAX_LEVEL = 10000 + +class ControlViewHolder( + val layout: ViewGroup, + val controlsController: ControlsController +) { + val icon: ImageView = layout.requireViewById(R.id.icon) + val status: TextView = layout.requireViewById(R.id.status) + val statusExtra: TextView = layout.requireViewById(R.id.status_extra) + val title: TextView = layout.requireViewById(R.id.title) + val subtitle: TextView = layout.requireViewById(R.id.subtitle) + val context: Context = layout.getContext() + val clipLayer: ClipDrawable + val gd: GradientDrawable + lateinit var cws: ControlWithState + + init { + val ld = layout.getBackground() as LayerDrawable + ld.mutate() + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable + gd = clipLayer.getDrawable() as GradientDrawable + } + + fun bindData(cws: ControlWithState) { + this.cws = cws + + val (status, template) = cws.control?.let { + title.setText(it.getTitle()) + subtitle.setText(it.getSubtitle()) + Pair(it.getStatus(), it.getControlTemplate()) + } ?: run { + title.setText(cws.ci.controlTitle) + subtitle.setText("") + Pair(Control.STATUS_UNKNOWN, ControlTemplate.NO_TEMPLATE) + } + + findBehavior(status, template).apply(this, cws) + } + + fun action(action: ControlAction) { + controlsController.action(cws.ci, action) + } + + private fun findBehavior(status: Int, template: ControlTemplate): Behavior { + return when { + status == Control.STATUS_UNKNOWN -> UnknownBehavior() + template is ToggleTemplate -> ToggleTemplateBehavior() + template is ToggleRangeTemplate -> ToggleRangeTemplateBehavior() + else -> { + object : Behavior { + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + cvh.status.setText(cws.control?.getStatusText()) + cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false)) + } + } + } + } + } + + internal fun applyRenderInfo(ri: RenderInfo) { + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + val bg = context.getResources().getColorStateList(ri.background, context.getTheme()) + status.setTextColor(fg) + statusExtra.setTextColor(fg) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) + + gd.setColor(bg) + } + + fun setEnabled(enabled: Boolean) { + status.setEnabled(enabled) + icon.setEnabled(enabled) + } +} + +private interface Behavior { + fun apply(cvh: ControlViewHolder, cws: ControlWithState) + + fun findRenderInfo(deviceType: Int, isActive: Boolean): RenderInfo = + deviceRenderMap.getOrDefault(deviceType, unknownDeviceMap).getValue(isActive) +} + +private class UnknownBehavior : Behavior { + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + cvh.status.setText("Loading...") + cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false)) + } +} + +private class ToggleRangeTemplateBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleRangeTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var rangeTemplate: RangeTemplate + lateinit var statusExtra: TextView + lateinit var status: TextView + lateinit var context: Context + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + + statusExtra = cvh.statusExtra + status = cvh.status + + status.setText(control.getStatusText()) + + context = status.getContext() + + cvh.layout.setOnTouchListener(ToggleRangeTouchListener()) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleRangeTemplate + rangeTemplate = template.getRange() + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + updateRange((rangeTemplate.getCurrentValue() / 100.0f), checked) + + cvh.setEnabled(checked) + cvh.applyRenderInfo(findRenderInfo(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } + + fun beginUpdateRange() { + status.setVisibility(View.GONE) + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) + } + + fun updateRange(f: Float, checked: Boolean) { + clipLayer.setLevel(if (checked) (MAX_LEVEL * f).toInt() else MIN_LEVEL) + + if (checked && f < 100.0f && f > 0.0f) { + statusExtra.setText("" + (f * 100.0).toInt() + "%") + statusExtra.setVisibility(View.VISIBLE) + } else { + statusExtra.setText("") + statusExtra.setVisibility(View.GONE) + } + } + + fun endUpdateRange(f: Float) { + statusExtra.setText(" - " + (f * 100.0).toInt() + "%") + + val newValue = rangeTemplate.getMinValue() + + (f * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) + + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_normal).toFloat()) + status.setVisibility(View.VISIBLE) + + cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(newValue))) + } + + fun findNearestStep(value: Float): Float { + var minDiff = 1000f + + var f = rangeTemplate.getMinValue() + while (f <= rangeTemplate.getMaxValue()) { + val currentDiff = Math.abs(value - f) + if (currentDiff < minDiff) { + minDiff = currentDiff + } else { + return f - rangeTemplate.getStepValue() + } + + f += rangeTemplate.getStepValue() + } + + return rangeTemplate.getMaxValue() + } + + inner class ToggleRangeTouchListener() : View.OnTouchListener { + private var initialTouchX: Float = 0.0f + private var initialTouchY: Float = 0.0f + private var isDragging: Boolean = false + private val minDragDiff = 20 + + override fun onTouch(v: View, e: MotionEvent): Boolean { + when (e.getActionMasked()) { + MotionEvent.ACTION_DOWN -> setupTouch(e) + MotionEvent.ACTION_MOVE -> detectDrag(v, e) + MotionEvent.ACTION_UP -> endTouch(v, e) + } + + return true + } + + private fun setupTouch(e: MotionEvent) { + initialTouchX = e.getX() + initialTouchY = e.getY() + } + + private fun detectDrag(v: View, e: MotionEvent) { + val xDiff = Math.abs(e.getX() - initialTouchX) + val yDiff = Math.abs(e.getY() - initialTouchY) + + if (xDiff < minDragDiff) { + isDragging = false + } else { + if (!isDragging) { + this@ToggleRangeTemplateBehavior.beginUpdateRange() + } + v.getParent().requestDisallowInterceptTouchEvent(true) + isDragging = true + if (yDiff > xDiff) { + endTouch(v, e) + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeTemplateBehavior.updateRange(percent, true) + } + } + } + + private fun endTouch(v: View, e: MotionEvent) { + if (!isDragging) { + this@ToggleRangeTemplateBehavior.toggle() + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeTemplateBehavior.endUpdateRange(percent) + } + + initialTouchX = 0.0f + initialTouchY = 0.0f + isDragging = false + } + } +} + +private class ToggleTemplateBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var context: Context + lateinit var status: TextView + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + status = cvh.status + + status.setText(control.getStatusText()) + + cvh.layout.setOnClickListener(View.OnClickListener() { toggle() }) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleTemplate + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL) + cvh.setEnabled(checked) + cvh.applyRenderInfo(findRenderInfo(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } +} + +internal data class RenderInfo(val iconResourceId: Int, val foreground: Int, val background: Int) + +private val unknownDeviceMap = mapOf( + false to RenderInfo( + R.drawable.ic_light_off_gm2_24px, + R.color.unknown_foreground, + R.color.unknown_foreground), + true to RenderInfo( + R.drawable.ic_lightbulb_outline_gm2_24px, + R.color.unknown_foreground, + R.color.unknown_foreground) +) + +private val deviceRenderMap = mapOf>( + DeviceTypes.TYPE_UNKNOWN to unknownDeviceMap, + DeviceTypes.TYPE_LIGHT to mapOf( + false to RenderInfo( + R.drawable.ic_light_off_gm2_24px, + R.color.light_foreground, + R.color.light_background), + true to RenderInfo( + R.drawable.ic_lightbulb_outline_gm2_24px, + R.color.light_foreground, + R.color.light_background) + ), + DeviceTypes.TYPE_THERMOSTAT to mapOf( + false to RenderInfo( + R.drawable.ic_device_thermostat_gm2_24px, + R.color.light_foreground, + R.color.light_background), + true to RenderInfo( + R.drawable.ic_device_thermostat_gm2_24px, + R.color.light_foreground, + R.color.light_background) + ), + DeviceTypes.TYPE_CAMERA to mapOf( + false to RenderInfo( + R.drawable.ic_videocam_gm2_24px, + R.color.light_foreground, + R.color.light_background), + true to RenderInfo( + R.drawable.ic_videocam_gm2_24px, + R.color.light_foreground, + R.color.light_background) + ), + DeviceTypes.TYPE_LOCK to mapOf( + false to RenderInfo( + R.drawable.ic_lock_open_gm2_24px, + R.color.lock_foreground, + R.color.lock_background), + true to RenderInfo( + R.drawable.ic_lock_gm2_24px, + R.color.lock_foreground, + R.color.lock_background) + ), + DeviceTypes.TYPE_SWITCH to mapOf( + false to RenderInfo( + R.drawable.ic_switches_gm2_24px, + R.color.lock_foreground, + R.color.lock_background), + true to RenderInfo( + R.drawable.ic_switches_gm2_24px, + R.color.lock_foreground, + R.color.lock_background) + ), + DeviceTypes.TYPE_OUTLET to mapOf( + false to RenderInfo( + R.drawable.ic_power_off_gm2_24px, + R.color.lock_foreground, + R.color.lock_background), + true to RenderInfo( + R.drawable.ic_power_gm2_24px, + R.color.lock_foreground, + R.color.lock_background) + ) +) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt new file mode 100644 index 0000000000000..816f0b2cb1d17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt @@ -0,0 +1,31 @@ +/* + * 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.service.controls.Control + +import com.android.systemui.controls.controller.ControlInfo + +/** + * A container for: + *
    + *
  • ControlInfo - Basic cached info about a Control + *
  • Control - Actual Control parcelable received directly from + * the participating application + *
+ */ +data class ControlWithState(val ci: ControlInfo, val control: Control?) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 0270c2b6b1b33..b07a75d5e7575 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -19,12 +19,15 @@ package com.android.systemui.controls.ui import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction +import android.view.ViewGroup interface ControlsUiController { + fun show(parent: ViewGroup) + fun hide() fun onRefreshState(componentName: ComponentName, controls: List) fun onActionResponse( componentName: ComponentName, controlId: String, @ControlAction.ResponseResult response: Int ) -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 0ace1263b49b8..926fb6e75594c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -16,19 +16,204 @@ package com.android.systemui.controls.ui +import android.accounts.Account +import android.accounts.AccountManager import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder import android.service.controls.Control +import android.service.controls.TokenProvider +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView + +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.R + +import dagger.Lazy + +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton +private const val TAG = "ControlsUi" + +// TEMP CODE for MOCK +private const val TOKEN = "https://www.googleapis.com/auth/assistant" +private const val SCOPE = "oauth2:" + TOKEN +private var tokenProviderConnection: TokenProviderConnection? = null +class TokenProviderConnection(val cc: ControlsController, val context: Context) + : ServiceConnection { + private var mTokenProvider: TokenProvider? = null + + override fun onServiceConnected(cName: ComponentName, binder: IBinder) { + Thread({ + Log.i(TAG, "TokenProviderConnection connected") + mTokenProvider = TokenProvider.Stub.asInterface(binder) + + val mLastAccountName = mTokenProvider?.getAccountName() + + if (mLastAccountName == null || mLastAccountName.isEmpty()) { + Log.e(TAG, "NO ACCOUNT IS SET. Open HomeMock app") + } else { + mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName)) + cc.subscribeToFavorites() + } + }, "TokenProviderThread").start() + } + + override fun onServiceDisconnected(cName: ComponentName) { + mTokenProvider = null + } + + fun getAuthToken(accountName: String): String? { + val am = AccountManager.get(context) + val accounts = am.getAccountsByType("com.google") + if (accounts == null || accounts.size == 0) { + Log.w(TAG, "No com.google accounts found") + return null + } + + var account: Account? = null + for (a in accounts) { + if (a.name.equals(accountName)) { + account = a + break + } + } + + if (account == null) { + account = accounts[0] + } + + try { + return am.blockingGetAuthToken(account!!, SCOPE, true) + } catch (e: Throwable) { + Log.e(TAG, "Error getting auth token", e) + return null + } + } +} + @Singleton -class ControlsUiControllerImpl @Inject constructor() : ControlsUiController { +class ControlsUiControllerImpl @Inject constructor ( + val controlsController: Lazy, + val context: Context, + @Main val uiExecutor: Executor +) : ControlsUiController { + + private lateinit var controlInfos: List + private val controlsById = mutableMapOf, ControlWithState>() + private val controlViewsById = mutableMapOf() + private lateinit var parent: ViewGroup + + override fun show(parent: ViewGroup) { + Log.d(TAG, "show()") + + this.parent = parent + + controlInfos = controlsController.get().getFavoriteControls() + + controlInfos.map { + ControlWithState(it, null) + }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) } + + if (controlInfos.isEmpty()) { + showInitialSetupView() + } else { + showControlsView() + } + + // Temp code to pass auth + tokenProviderConnection = TokenProviderConnection(controlsController.get(), context) + val serviceIntent = Intent() + serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock", + "com.android.systemui.home.mock.AuthService")) + context.bindService(serviceIntent, tokenProviderConnection!!, Context.BIND_AUTO_CREATE) + } + + private fun showInitialSetupView() { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.controls_no_favorites, parent, true) + + val textView = parent.requireViewById(R.id.controls_title) as TextView + textView.setOnClickListener { + val i = Intent() + i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.startActivity(i) + } + } + + private fun showControlsView() { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.controls_with_favorites, parent, true) + + val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup + var lastRow: ViewGroup = createRow(inflater, listView) + controlInfos.forEach { + Log.d(TAG, "favorited control id: " + it.controlId) + if (lastRow.getChildCount() == 2) { + lastRow = createRow(inflater, listView) + } + val item = inflater.inflate( + R.layout.controls_base_item, lastRow, false) as ViewGroup + lastRow.addView(item) + val cvh = ControlViewHolder(item, controlsController.get()) + cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!) + controlViewsById.put(it.controlId, cvh) + } + + val moreImageView = parent.requireViewById(R.id.controls_more) as View + moreImageView.setOnClickListener { + val i = Intent() + i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.startActivity(i) + } + } + + override fun hide() { + Log.d(TAG, "hide()") + controlsController.get().unsubscribe() + context.unbindService(tokenProviderConnection) + tokenProviderConnection = null + + parent.removeAllViews() + controlsById.clear() + controlViewsById.clear() + } override fun onRefreshState(componentName: ComponentName, controls: List) { - TODO("not implemented") + Log.d(TAG, "onRefreshState()") + controls.forEach { c -> + controlsById.get(Pair(componentName, c.getControlId()))?.let { + Log.d(TAG, "onRefreshState() for id: " + c.getControlId()) + val cws = ControlWithState(it.ci, c) + controlsById.put(Pair(componentName, c.getControlId()), cws) + + uiExecutor.execute { + controlViewsById.get(c.getControlId())?.bindData(cws) + } + } + } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { + Log.d(TAG, "onActionResponse()") TODO("not implemented") } -} \ No newline at end of file + + private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup { + val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup + parent.addView(row) + return row + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 83f6d45465b3f..80d776a59235c 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -93,12 +93,13 @@ import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.BlurUtils; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -183,6 +184,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IStatusBarService mStatusBarService; private final NotificationShadeWindowController mNotificationShadeWindowController; private GlobalActionsPanelPlugin mPanelPlugin; + private ControlsUiController mControlsUiController; /** * @param context everything needs a context :( @@ -200,7 +202,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, TelecomManager telecomManager, MetricsLogger metricsLogger, BlurUtils blurUtils, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, - NotificationShadeWindowController notificationShadeWindowController) { + NotificationShadeWindowController notificationShadeWindowController, + ControlsUiController controlsUiController) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -220,6 +223,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; + mControlsUiController = controlsUiController; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -455,9 +459,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardManager.isDeviceLocked()) : null; + boolean showControls = !mKeyguardManager.isDeviceLocked() && isControlsEnabled(mContext); + ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController, mBlurUtils, mSysuiColorExtractor, mStatusBarService, - mNotificationShadeWindowController, isControlsEnabled(mContext)); + mNotificationShadeWindowController, + showControls ? mControlsUiController : null); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -1543,13 +1550,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mHadTopUi; private final NotificationShadeWindowController mNotificationShadeWindowController; private final BlurUtils mBlurUtils; - private final boolean mControlsEnabled; + + private ControlsUiController mControlsUiController; + private ViewGroup mControlsView; ActionsDialog(Context context, MyAdapter adapter, GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - boolean controlsEnabled) { + ControlsUiController controlsUiController) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; @@ -1557,7 +1566,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; - mControlsEnabled = controlsEnabled; + mControlsUiController = controlsUiController; // Window initialization Window window = getWindow(); @@ -1639,6 +1648,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void initializeLayout() { setContentView(getGlobalActionsLayoutId(mContext)); fixNavBarClipping(); + mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls); mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); @@ -1674,7 +1684,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { - if (mControlsEnabled) { + if (mControlsUiController != null) { return com.android.systemui.R.layout.global_actions_grid_v2; } @@ -1758,6 +1768,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mBlurUtils.radiusForRatio(animatedValue)); }) .start(); + if (mControlsUiController != null) { + mControlsUiController.show(mControlsView); + } } @Override @@ -1766,6 +1779,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } mShowing = false; + if (mControlsUiController != null) mControlsUiController.hide(); mGlobalActionsLayout.setTranslationX(0); mGlobalActionsLayout.setTranslationY(0); mGlobalActionsLayout.setAlpha(1);