Controls in QS
Continue to support launching from both global actions as well as the QS tile. Use an activity for QS tile launches, which supports lockscreen visibility. Bug: 172360102 Test: manual Change-Id: I002aa32f9126745ee310e203ad45f9c28f39fa8d
This commit is contained in:
@@ -751,6 +751,17 @@
|
||||
android:visibleToInstantApps="true">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".controls.ui.ControlsActivity"
|
||||
android:label="@string/quick_controls_title"
|
||||
android:theme="@style/Theme.ControlsActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:showWhenLocked="true"
|
||||
android:showForAllUsers="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
|
||||
android:visibleToInstantApps="true">
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".controls.management.ControlsRequestReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?android:attr/colorBackground" />
|
||||
<corners android:radius="@dimen/notification_corner_radius" />
|
||||
</shape>
|
||||
@@ -20,8 +20,8 @@
|
||||
android:id="@+id/control_detail_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/controls_activity_view_top_offset"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:background="@android:color/black">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -30,7 +30,7 @@
|
||||
<ImageView
|
||||
android:id="@+id/control_detail_close"
|
||||
android:contentDescription="@string/accessibility_desc_close"
|
||||
android:src="@drawable/ic_close"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:tint="@color/control_primary_text"
|
||||
android:layout_width="48dp"
|
||||
@@ -56,7 +56,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/black"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -20,11 +20,8 @@
|
||||
android:id="@+id/control_detail_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="@dimen/controls_activity_view_top_offset"
|
||||
android:layout_marginHorizontal="@dimen/controls_activity_view_side_offset"
|
||||
android:padding="8dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/controls_dialog_bg">
|
||||
android:background="@android:color/black">
|
||||
|
||||
<com.android.systemui.globalactions.MinHeightScrollView
|
||||
android:layout_width="match_parent"
|
||||
@@ -25,8 +25,21 @@
|
||||
|
||||
<!-- make sure the header stays centered in the layout by adding a spacer -->
|
||||
<Space
|
||||
android:id="@+id/controls_spacer"
|
||||
android:layout_width="@dimen/controls_header_menu_size"
|
||||
android:layout_height="1dp" />
|
||||
android:layout_height="1dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/controls_close"
|
||||
android:contentDescription="@string/accessibility_desc_close"
|
||||
android:src="@drawable/ic_close"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:tint="@color/control_primary_text"
|
||||
android:layout_width="@dimen/controls_header_menu_size"
|
||||
android:layout_height="@dimen/controls_header_menu_size"
|
||||
android:padding="12dp"
|
||||
android:visibility="gone" />
|
||||
<!-- need to keep this outer view in order to have a correctly sized anchor
|
||||
for the dropdown menu, as well as dropdown background in the right place -->
|
||||
<LinearLayout
|
||||
|
||||
@@ -643,6 +643,16 @@
|
||||
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.ControlsActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
|
||||
<item name="android:windowActivityTransitions">true</item>
|
||||
<item name="android:windowContentTransitions">false</item>
|
||||
<item name="android:windowIsTranslucent">false</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
<item name="android:statusBarColor">@*android:color/transparent</item>
|
||||
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.CreateUser" parent="@style/Theme.SystemUI">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">#33000000</item>
|
||||
@@ -654,30 +664,12 @@
|
||||
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
|
||||
<item name="android:windowAnimationStyle">@style/Animation.Bottomsheet</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.Bottomsheet">
|
||||
<item name="android:windowEnterAnimation">@anim/bottomsheet_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/bottomsheet_out</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
|
||||
<item name="android:windowAnimationStyle">@style/Animation.ControlDialog</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.ControlDialog">
|
||||
<item name="android:windowEnterAnimation">@*android:anim/dialog_enter</item>
|
||||
<item name="android:windowExitAnimation">@*android:anim/dialog_exit</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Control" />
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||
import com.android.systemui.controls.management.ControlsRequestDialog
|
||||
import com.android.systemui.controls.ui.ControlActionCoordinator
|
||||
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
|
||||
import com.android.systemui.controls.ui.ControlsActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.controls.ui.ControlsUiControllerImpl
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
@@ -113,4 +114,9 @@ abstract class ControlsModule {
|
||||
abstract fun provideControlsRequestDialog(
|
||||
activity: ControlsRequestDialog
|
||||
): Activity
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(ControlsActivity::class)
|
||||
abstract fun provideControlsActivity(activity: ControlsActivity): Activity
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||
import com.android.systemui.controls.controller.StructureInfo
|
||||
import com.android.systemui.controls.ui.ControlsDialog
|
||||
import com.android.systemui.controls.ui.ControlsActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.globalactions.GlobalActionsComponent
|
||||
import com.android.systemui.settings.CurrentUserTracker
|
||||
@@ -112,7 +112,11 @@ class ControlsEditingActivity @Inject constructor(
|
||||
if (backToGlobalActions) {
|
||||
globalActionsComponent.handleShowGlobalActionsMenu()
|
||||
} else {
|
||||
ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
|
||||
val i = Intent().apply {
|
||||
component = ComponentName(applicationContext, ControlsActivity::class.java)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
startActivity(i)
|
||||
}
|
||||
animateExitAndFinish()
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.TooltipManager
|
||||
import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||
import com.android.systemui.controls.controller.StructureInfo
|
||||
import com.android.systemui.controls.ui.ControlsDialog
|
||||
import com.android.systemui.controls.ui.ControlsActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.globalactions.GlobalActionsComponent
|
||||
@@ -352,7 +352,11 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
if (backToGlobalActions) {
|
||||
globalActionsComponent.handleShowGlobalActionsMenu()
|
||||
} else {
|
||||
ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
|
||||
val i = Intent().apply {
|
||||
component = ComponentName(applicationContext, ControlsActivity::class.java)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
startActivity(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.controller.ControlsController
|
||||
import com.android.systemui.controls.ui.ControlsDialog
|
||||
import com.android.systemui.controls.ui.ControlsActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
@@ -116,7 +116,11 @@ class ControlsProviderSelectorActivity @Inject constructor(
|
||||
if (backToGlobalActions) {
|
||||
globalActionsComponent.handleShowGlobalActionsMenu()
|
||||
} else {
|
||||
ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
|
||||
val i = Intent().apply {
|
||||
component = ComponentName(applicationContext, ControlsActivity::class.java)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
startActivity(i)
|
||||
}
|
||||
animateExitAndFinish()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.controls.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.service.controls.Control
|
||||
|
||||
/**
|
||||
@@ -24,8 +25,8 @@ import android.service.controls.Control
|
||||
*/
|
||||
interface ControlActionCoordinator {
|
||||
|
||||
// Handle actions launched from GlobalActionsDialog or ControlDialog
|
||||
var startedFromGlobalActions: Boolean
|
||||
// If launched from an Activity, continue within this stack
|
||||
var activityContext: Context?
|
||||
|
||||
/**
|
||||
* Close any dialogs which may have been open
|
||||
|
||||
@@ -18,6 +18,7 @@ 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
|
||||
@@ -60,7 +61,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
private var pendingAction: Action? = null
|
||||
private var actionsInProgress = mutableSetOf<String>()
|
||||
|
||||
override var startedFromGlobalActions: Boolean = true
|
||||
override var activityContext: Context? = null
|
||||
|
||||
companion object {
|
||||
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
|
||||
@@ -83,7 +84,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
|
||||
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
||||
if (cvh.usePanel()) {
|
||||
showDialog(cvh, control.getAppIntent().getIntent())
|
||||
showDetail(cvh, control.getAppIntent().getIntent())
|
||||
} else {
|
||||
cvh.action(CommandAction(templateId))
|
||||
}
|
||||
@@ -109,7 +110,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
// 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())
|
||||
showDetail(cvh, it.getAppIntent().getIntent())
|
||||
}
|
||||
}, false /* blockable */))
|
||||
}
|
||||
@@ -151,10 +152,16 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
activityStarter.dismissKeyguardThenExecute({
|
||||
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
|
||||
if (closeDialog) {
|
||||
if (startedFromGlobalActions) {
|
||||
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 {
|
||||
globalActionsComponent.handleShowGlobalActionsMenu()
|
||||
} else {
|
||||
ControlsDialog(context, broadcastDispatcher).show(lazyUiController.get())
|
||||
}
|
||||
} else {
|
||||
action.invoke()
|
||||
@@ -170,9 +177,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
bgExecutor.execute { vibrator.vibrate(effect) }
|
||||
}
|
||||
|
||||
private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
|
||||
private fun showDetail(cvh: ControlViewHolder, intent: Intent) {
|
||||
bgExecutor.execute {
|
||||
val activities: List<ResolveInfo> = cvh.context.packageManager.queryIntentActivities(
|
||||
val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities(
|
||||
intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY
|
||||
)
|
||||
@@ -180,8 +187,8 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
||||
uiExecutor.execute {
|
||||
// make sure the intent is valid before attempting to open the dialog
|
||||
if (activities.isNotEmpty() && taskViewFactory.isPresent) {
|
||||
taskViewFactory.get().create(cvh.context, uiExecutor, {
|
||||
dialog = DetailDialog(cvh, it, intent).also {
|
||||
taskViewFactory.get().create(context, uiExecutor, {
|
||||
dialog = DetailDialog(activityContext, it, intent, cvh).also {
|
||||
it.setOnDismissListener { _ -> dialog = null }
|
||||
it.show()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsets.Type
|
||||
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.controls.management.ControlsAnimations
|
||||
import com.android.systemui.util.LifecycleActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Displays Device Controls inside an activity
|
||||
*/
|
||||
class ControlsActivity @Inject constructor(
|
||||
private val uiController: ControlsUiController
|
||||
) : LifecycleActivity() {
|
||||
|
||||
private lateinit var parent: ViewGroup
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.controls_fullscreen)
|
||||
|
||||
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
|
||||
setOnApplyWindowInsetsListener {
|
||||
v: View, insets: WindowInsets ->
|
||||
v.apply {
|
||||
val l = getPaddingLeft()
|
||||
val t = getPaddingTop()
|
||||
val r = getPaddingRight()
|
||||
setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
|
||||
}
|
||||
|
||||
WindowInsets.CONSUMED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
|
||||
parent.alpha = 0f
|
||||
uiController.show(parent, { animateExitAndFinish() }, this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
ControlsAnimations.enterAnimation(parent).start()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
animateExitAndFinish()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
uiController.hide()
|
||||
}
|
||||
|
||||
private fun animateExitAndFinish() {
|
||||
ControlsAnimations.exitAnimation(parent, { finish() }).start()
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
|
||||
import com.android.systemui.Interpolators
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Show the controls space inside a dialog, as from the lock screen.
|
||||
*/
|
||||
class ControlsDialog @Inject constructor(
|
||||
thisContext: Context,
|
||||
val broadcastDispatcher: BroadcastDispatcher
|
||||
) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.getAction()
|
||||
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
|
||||
or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
||||
|
||||
setContentView(R.layout.controls_in_dialog)
|
||||
|
||||
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
|
||||
setOnClickListener { dismiss() }
|
||||
(getParent() as View).setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
fun show(
|
||||
controller: ControlsUiController
|
||||
): ControlsDialog {
|
||||
super.show()
|
||||
|
||||
val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls)
|
||||
vg.alpha = 0f
|
||||
controller.show(vg, { dismiss() }, false /* startedFromGlobalActions */)
|
||||
|
||||
vg.animate()
|
||||
.alpha(1f)
|
||||
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
|
||||
.setDuration(300)
|
||||
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
|
||||
broadcastDispatcher.registerReceiver(receiver, filter)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
broadcastDispatcher.unregisterReceiver(receiver)
|
||||
|
||||
if (!isShowing()) return
|
||||
|
||||
super.dismiss()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.controls.ui
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.actions.ControlAction
|
||||
import android.view.ViewGroup
|
||||
@@ -30,7 +31,7 @@ interface ControlsUiController {
|
||||
public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions"
|
||||
}
|
||||
|
||||
fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean)
|
||||
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context?)
|
||||
fun hide()
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,7 +148,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
override fun show(
|
||||
parent: ViewGroup,
|
||||
onDismiss: Runnable,
|
||||
startedFromGlobalActions: Boolean
|
||||
activityContext: Context?
|
||||
) {
|
||||
Log.d(ControlsUiController.TAG, "show()")
|
||||
this.parent = parent
|
||||
@@ -156,7 +156,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
hidden = false
|
||||
retainCache = false
|
||||
|
||||
controlActionCoordinator.startedFromGlobalActions = startedFromGlobalActions
|
||||
controlActionCoordinator.activityContext = activityContext
|
||||
|
||||
allStructures = controlsController.get().getFavorites()
|
||||
selectedStructure = loadPreference(allStructures)
|
||||
@@ -193,7 +193,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
controlViewsById.clear()
|
||||
controlsById.clear()
|
||||
|
||||
show(parent, onDismiss, controlActionCoordinator.startedFromGlobalActions)
|
||||
show(parent, onDismiss, controlActionCoordinator.activityContext)
|
||||
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
|
||||
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
|
||||
showAnim.setDuration(FADE_IN_MILLIS)
|
||||
@@ -268,7 +268,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
|
||||
intent.putExtra(
|
||||
ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
|
||||
controlActionCoordinator.startedFromGlobalActions
|
||||
controlActionCoordinator.activityContext == null
|
||||
)
|
||||
onDismiss.run()
|
||||
|
||||
@@ -392,6 +392,17 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
val inflater = LayoutInflater.from(context)
|
||||
inflater.inflate(R.layout.controls_with_favorites, parent, true)
|
||||
|
||||
if (controlActionCoordinator.activityContext == null) {
|
||||
parent.requireViewById<View>(R.id.controls_spacer).apply {
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
parent.requireViewById<ImageView>(R.id.controls_close).apply {
|
||||
setOnClickListener { _: View -> onDismiss.run() }
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
val maxColumns = findMaxColumns()
|
||||
|
||||
val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
|
||||
@@ -502,6 +513,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
override fun hide() {
|
||||
hidden = true
|
||||
|
||||
controlActionCoordinator.activityContext = null
|
||||
|
||||
closeDialogs(true)
|
||||
controlsController.get().unsubscribe()
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import android.app.ActivityTaskManager.INVALID_TASK_ID
|
||||
import android.app.Dialog
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
@@ -35,18 +35,20 @@ import com.android.systemui.R
|
||||
import com.android.wm.shell.TaskView
|
||||
|
||||
/**
|
||||
* A dialog that provides an {@link ActivityView}, allowing the application to provide
|
||||
* A dialog that provides an {@link TaskView}, allowing the application to provide
|
||||
* additional information and actions pertaining to a {@link android.service.controls.Control}.
|
||||
* The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}.
|
||||
*/
|
||||
class DetailDialog(
|
||||
val cvh: ControlViewHolder,
|
||||
val activityView: TaskView,
|
||||
val intent: Intent
|
||||
) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) {
|
||||
|
||||
val activityContext: Context?,
|
||||
val taskView: TaskView,
|
||||
val intent: Intent,
|
||||
val cvh: ControlViewHolder
|
||||
) : Dialog(
|
||||
activityContext ?: cvh.context,
|
||||
R.style.Theme_SystemUI_Dialog_Control_DetailPanel
|
||||
) {
|
||||
companion object {
|
||||
private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset"
|
||||
/*
|
||||
* Indicate to the activity that it is being rendered in a bottomsheet, and they
|
||||
* should optimize the layout for a smaller space.
|
||||
@@ -71,10 +73,19 @@ class DetailDialog(
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
|
||||
activityView.startActivity(
|
||||
PendingIntent.getActivity(context, 0, launchIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE),
|
||||
null, ActivityOptions.makeBasic())
|
||||
val options = activityContext?.let {
|
||||
ActivityOptions.makeCustomAnimation(
|
||||
it,
|
||||
0 /* enterResId */,
|
||||
0 /* exitResId */
|
||||
)
|
||||
} ?: ActivityOptions.makeBasic()
|
||||
taskView.startActivity(
|
||||
PendingIntent.getActivity(context, 0, launchIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE),
|
||||
null,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTaskRemovalStarted(taskId: Int) {
|
||||
@@ -92,7 +103,10 @@ class DetailDialog(
|
||||
}
|
||||
|
||||
init {
|
||||
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
|
||||
if (activityContext == null) {
|
||||
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
|
||||
}
|
||||
|
||||
// To pass touches to the task inside TaskView.
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
||||
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
|
||||
@@ -100,7 +114,7 @@ class DetailDialog(
|
||||
setContentView(R.layout.controls_detail_dialog)
|
||||
|
||||
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
|
||||
addView(activityView)
|
||||
addView(taskView)
|
||||
}
|
||||
|
||||
requireViewById<ImageView>(R.id.control_detail_close).apply {
|
||||
@@ -120,48 +134,34 @@ class DetailDialog(
|
||||
|
||||
// consume all insets to achieve slide under effect
|
||||
window.getDecorView().setOnApplyWindowInsetsListener {
|
||||
_: View, insets: WindowInsets ->
|
||||
activityView.apply {
|
||||
v: View, insets: WindowInsets ->
|
||||
taskView.apply {
|
||||
val l = getPaddingLeft()
|
||||
val t = getPaddingTop()
|
||||
val r = getPaddingRight()
|
||||
setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
|
||||
}
|
||||
|
||||
val l = v.getPaddingLeft()
|
||||
val b = v.getPaddingBottom()
|
||||
val r = v.getPaddingRight()
|
||||
v.setPadding(l, insets.getInsets(Type.systemBars()).top, r, b)
|
||||
|
||||
WindowInsets.CONSUMED
|
||||
}
|
||||
|
||||
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
|
||||
// use flag only temporarily for testing
|
||||
val resolver = cvh.context.contentResolver
|
||||
val defaultOffsetInPx = cvh.context.resources
|
||||
.getDimensionPixelSize(R.dimen.controls_activity_view_top_offset)
|
||||
val offsetInPx = Settings.Secure.getInt(resolver, PANEL_TOP_OFFSET, defaultOffsetInPx)
|
||||
|
||||
val lp = getLayoutParams() as ViewGroup.MarginLayoutParams
|
||||
lp.topMargin = offsetInPx
|
||||
setLayoutParams(lp)
|
||||
|
||||
setOnClickListener { dismiss() }
|
||||
(getParent() as View).setOnClickListener { dismiss() }
|
||||
}
|
||||
|
||||
if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(context.getResources())) {
|
||||
val cornerRadius = context.resources
|
||||
.getDimensionPixelSize(R.dimen.controls_activity_view_corner_radius)
|
||||
activityView.setCornerRadius(cornerRadius.toFloat())
|
||||
taskView.setCornerRadius(cornerRadius.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
activityView.setListener(cvh.uiExecutor, stateCallback)
|
||||
|
||||
super.show()
|
||||
taskView.setListener(cvh.uiExecutor, stateCallback)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
if (!isShowing()) return
|
||||
activityView.release()
|
||||
taskView.release()
|
||||
|
||||
super.dismiss()
|
||||
}
|
||||
|
||||
@@ -2236,7 +2236,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
private void showControls(ControlsUiController controller) {
|
||||
mControlsUiController = controller;
|
||||
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
|
||||
true /* startedFromGlobalActions */);
|
||||
null /* activityContext */);
|
||||
}
|
||||
|
||||
private boolean isWalletViewAvailable() {
|
||||
@@ -2465,7 +2465,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
});
|
||||
if (mControlsUiController != null) {
|
||||
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
|
||||
true /* startedFromGlobalActions */);
|
||||
null /* activityContext */);
|
||||
}
|
||||
|
||||
mBackgroundDrawable.setAlpha(0);
|
||||
@@ -2641,7 +2641,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
mGlobalActionsLayout.updateList();
|
||||
if (mControlsUiController != null) {
|
||||
mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
|
||||
true /* startedFromGlobalActions */);
|
||||
null /* activityContext */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.qs.tiles
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -27,7 +28,8 @@ import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.dagger.ControlsComponent
|
||||
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsDialog
|
||||
import com.android.systemui.controls.ui.ControlsActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
@@ -40,7 +42,6 @@ import com.android.systemui.statusbar.FeatureFlags
|
||||
import com.android.systemui.util.settings.GlobalSettings
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class DeviceControlsTile @Inject constructor(
|
||||
host: QSHost,
|
||||
@@ -52,7 +53,6 @@ class DeviceControlsTile @Inject constructor(
|
||||
qsLogger: QSLogger,
|
||||
private val controlsComponent: ControlsComponent,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val dialogProvider: Provider<ControlsDialog>,
|
||||
globalSettings: GlobalSettings
|
||||
) : QSTileImpl<QSTile.State>(
|
||||
host,
|
||||
@@ -72,7 +72,6 @@ class DeviceControlsTile @Inject constructor(
|
||||
private var hasControlsApps = AtomicBoolean(false)
|
||||
private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
|
||||
|
||||
private var controlsDialog: ControlsDialog? = null
|
||||
private val icon = ResourceIcon.get(R.drawable.ic_device_light)
|
||||
|
||||
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
|
||||
@@ -102,27 +101,19 @@ class DeviceControlsTile @Inject constructor(
|
||||
}
|
||||
|
||||
override fun handleDestroy() {
|
||||
dismissDialog()
|
||||
super.handleDestroy()
|
||||
}
|
||||
|
||||
private fun createDialog() {
|
||||
if (controlsDialog?.isShowing != true) {
|
||||
controlsDialog = dialogProvider.get()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissDialog() {
|
||||
controlsDialog?.dismiss()?.also {
|
||||
controlsDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleClick() {
|
||||
if (state.state == Tile.STATE_ACTIVE) {
|
||||
mUiHandler.post {
|
||||
createDialog()
|
||||
controlsDialog?.show(controlsComponent.getControlsUiController().get())
|
||||
mHost.collapsePanels()
|
||||
val i = Intent().apply {
|
||||
component = ComponentName(mContext, ControlsActivity::class.java)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
|
||||
}
|
||||
mContext.startActivity(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +124,6 @@ class DeviceControlsTile @Inject constructor(
|
||||
state.contentDescription = state.label
|
||||
state.icon = icon
|
||||
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
|
||||
if (controlsDialog == null) {
|
||||
mUiHandler.post(this::createDialog)
|
||||
}
|
||||
if (controlsComponent.getVisibility() == AVAILABLE) {
|
||||
state.state = Tile.STATE_ACTIVE
|
||||
state.secondaryLabel = ""
|
||||
@@ -146,7 +134,6 @@ class DeviceControlsTile @Inject constructor(
|
||||
state.stateDescription = state.secondaryLabel
|
||||
} else {
|
||||
state.state = Tile.STATE_UNAVAILABLE
|
||||
dismissDialog()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.systemui.qs.tiles
|
||||
|
||||
import android.os.Handler
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.service.quicksettings.Tile
|
||||
import android.testing.AndroidTestingRunner
|
||||
@@ -30,7 +32,6 @@ import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.controller.ControlsController
|
||||
import com.android.systemui.controls.dagger.ControlsComponent
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsDialog
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
@@ -50,7 +51,9 @@ import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.doNothing
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.Optional
|
||||
@@ -80,8 +83,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
private lateinit var controlsController: ControlsController
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var controlsDialog: ControlsDialog
|
||||
private lateinit var globalSettings: GlobalSettings
|
||||
@Mock
|
||||
private lateinit var serviceInfo: ControlsServiceInfo
|
||||
@@ -95,6 +96,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
private lateinit var tile: DeviceControlsTile
|
||||
|
||||
private lateinit var secureSettings: SecureSettings
|
||||
private lateinit var spiedContext: Context
|
||||
private var featureEnabled = true
|
||||
|
||||
@Before
|
||||
@@ -103,7 +105,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
testableLooper = TestableLooper.get(this)
|
||||
secureSettings = FakeSettings()
|
||||
|
||||
`when`(qsHost.context).thenReturn(mContext)
|
||||
spiedContext = spy(mContext)
|
||||
doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
|
||||
`when`(qsHost.context).thenReturn(spiedContext)
|
||||
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
|
||||
`when`(controlsController.available).thenReturn(true)
|
||||
`when`(controlsComponent.isEnabled()).thenReturn(true)
|
||||
@@ -276,7 +280,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
tile.click()
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
|
||||
verify(spiedContext, never()).startActivity(any(Intent::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -293,7 +297,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
tile.click()
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(controlsDialog).show(controlsUiController)
|
||||
verify(spiedContext).startActivity(any(Intent::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -311,25 +315,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
tile.click()
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDialogDismissedOnDestroy() {
|
||||
verify(controlsListingController).observe(
|
||||
any(LifecycleOwner::class.java),
|
||||
capture(listingCallbackCaptor)
|
||||
)
|
||||
|
||||
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
tile.click()
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
tile.destroy()
|
||||
testableLooper.processAllMessages()
|
||||
verify(controlsDialog).dismiss()
|
||||
verify(spiedContext, never()).startActivity(any(Intent::class.java))
|
||||
}
|
||||
|
||||
private fun createTile(): DeviceControlsTile {
|
||||
@@ -343,7 +329,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
||||
qsLogger,
|
||||
controlsComponent,
|
||||
featureFlags,
|
||||
{ controlsDialog },
|
||||
globalSettings
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user