Merge "Add Open app in overflow menu" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
2f927cffc8
@@ -189,9 +189,9 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
authorizedPanelsRepository.addAuthorizedPanels(
|
authorizedPanelsRepository.addAuthorizedPanels(
|
||||||
setOf(serviceInfo.componentName.packageName)
|
setOf(serviceInfo.componentName.packageName)
|
||||||
)
|
)
|
||||||
animateExitAndFinish()
|
|
||||||
val selected = SelectedItem.PanelItem(appName, componentName)
|
val selected = SelectedItem.PanelItem(appName, componentName)
|
||||||
controlsController.setPreferredSelection(selected)
|
controlsController.setPreferredSelection(selected)
|
||||||
|
animateExitAndFinish()
|
||||||
openControlsOrigin()
|
openControlsOrigin()
|
||||||
}
|
}
|
||||||
dialog = null
|
dialog = null
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.app.PendingIntent
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.service.controls.Control
|
import android.service.controls.Control
|
||||||
@@ -38,6 +39,7 @@ import android.view.animation.AccelerateInterpolator
|
|||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.BaseAdapter
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
@@ -90,6 +92,7 @@ private data class ControlKey(val componentName: ComponentName, val controlId: S
|
|||||||
class ControlsUiControllerImpl @Inject constructor (
|
class ControlsUiControllerImpl @Inject constructor (
|
||||||
val controlsController: Lazy<ControlsController>,
|
val controlsController: Lazy<ControlsController>,
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
private val packageManager: PackageManager,
|
||||||
@Main val uiExecutor: DelayableExecutor,
|
@Main val uiExecutor: DelayableExecutor,
|
||||||
@Background val bgExecutor: DelayableExecutor,
|
@Background val bgExecutor: DelayableExecutor,
|
||||||
val controlsListingController: Lazy<ControlsListingController>,
|
val controlsListingController: Lazy<ControlsListingController>,
|
||||||
@@ -113,6 +116,11 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private const val PREF_IS_PANEL = "controls_is_panel"
|
private const val PREF_IS_PANEL = "controls_is_panel"
|
||||||
|
|
||||||
private const val FADE_IN_MILLIS = 200L
|
private const val FADE_IN_MILLIS = 200L
|
||||||
|
|
||||||
|
private const val OPEN_APP_ID = 0L
|
||||||
|
private const val ADD_CONTROLS_ID = 1L
|
||||||
|
private const val ADD_APP_ID = 2L
|
||||||
|
private const val EDIT_CONTROLS_ID = 3L
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
||||||
@@ -140,6 +148,9 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
it.getTitle()
|
it.getTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var openAppIntent: Intent? = null
|
||||||
|
private var overflowMenuAdapter: BaseAdapter? = null
|
||||||
|
|
||||||
private val onSeedingComplete = Consumer<Boolean> {
|
private val onSeedingComplete = Consumer<Boolean> {
|
||||||
accepted ->
|
accepted ->
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
@@ -216,6 +227,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
this.onDismiss = onDismiss
|
this.onDismiss = onDismiss
|
||||||
this.activityContext = activityContext
|
this.activityContext = activityContext
|
||||||
|
this.openAppIntent = null
|
||||||
|
this.overflowMenuAdapter = null
|
||||||
hidden = false
|
hidden = false
|
||||||
retainCache = false
|
retainCache = false
|
||||||
|
|
||||||
@@ -306,6 +319,12 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
startTargetedActivity(si, ControlsEditingActivity::class.java)
|
startTargetedActivity(si, ControlsEditingActivity::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startDefaultActivity() {
|
||||||
|
openAppIntent?.let {
|
||||||
|
startActivity(it, animateExtra = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
|
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
|
||||||
val i = Intent(activityContext, klazz)
|
val i = Intent(activityContext, klazz)
|
||||||
putIntentExtras(i, si)
|
putIntentExtras(i, si)
|
||||||
@@ -329,9 +348,11 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
startActivity(i)
|
startActivity(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startActivity(intent: Intent) {
|
private fun startActivity(intent: Intent, animateExtra: Boolean = true) {
|
||||||
// Force animations when transitioning from a dialog to an activity
|
// Force animations when transitioning from a dialog to an activity
|
||||||
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
|
if (animateExtra) {
|
||||||
|
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
|
||||||
|
}
|
||||||
|
|
||||||
if (keyguardStateController.isShowing()) {
|
if (keyguardStateController.isShowing()) {
|
||||||
activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
|
activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
|
||||||
@@ -383,8 +404,31 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
|
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bgExecutor.execute {
|
||||||
|
val intent = Intent(Intent.ACTION_MAIN)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
.setPackage(selectionItem.componentName.packageName)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||||
|
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
|
||||||
|
val intents = packageManager
|
||||||
|
.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L))
|
||||||
|
intents.firstOrNull { it.activityInfo.exported }?.let { resolved ->
|
||||||
|
intent.setPackage(null)
|
||||||
|
intent.setComponent(resolved.activityInfo.componentName)
|
||||||
|
openAppIntent = intent
|
||||||
|
parent.post {
|
||||||
|
// This will call show on the PopupWindow in the same thread, so make sure this
|
||||||
|
// happens in the view thread.
|
||||||
|
overflowMenuAdapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
createDropDown(panelsAndStructures, selectionItem)
|
createDropDown(panelsAndStructures, selectionItem)
|
||||||
createMenu()
|
|
||||||
|
val currentApps = panelsAndStructures.map { it.componentName }.toSet()
|
||||||
|
val allApps = controlsListingController.get()
|
||||||
|
.getCurrentServices().map { it.componentName }.toSet()
|
||||||
|
createMenu(extraApps = (allApps - currentApps).isNotEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPanelView(componentName: ComponentName) {
|
private fun createPanelView(componentName: ComponentName) {
|
||||||
@@ -423,28 +467,41 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMenu() {
|
private fun createMenu(extraApps: Boolean) {
|
||||||
val isPanel = selectedItem is SelectedItem.PanelItem
|
val isPanel = selectedItem is SelectedItem.PanelItem
|
||||||
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
||||||
?: EMPTY_STRUCTURE
|
?: EMPTY_STRUCTURE
|
||||||
val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
|
val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
|
||||||
val addControlsId = if (newFlows || isPanel) {
|
|
||||||
R.string.controls_menu_add_another_app
|
val items = buildList {
|
||||||
} else {
|
add(OverflowMenuAdapter.MenuItem(
|
||||||
R.string.controls_menu_add
|
context.getText(R.string.controls_open_app),
|
||||||
|
OPEN_APP_ID
|
||||||
|
))
|
||||||
|
if (newFlows || isPanel) {
|
||||||
|
if (extraApps) {
|
||||||
|
add(OverflowMenuAdapter.MenuItem(
|
||||||
|
context.getText(R.string.controls_menu_add_another_app),
|
||||||
|
ADD_APP_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add(OverflowMenuAdapter.MenuItem(
|
||||||
|
context.getText(R.string.controls_menu_add),
|
||||||
|
ADD_CONTROLS_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if (!isPanel) {
|
||||||
|
add(OverflowMenuAdapter.MenuItem(
|
||||||
|
context.getText(R.string.controls_menu_edit),
|
||||||
|
EDIT_CONTROLS_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val items = if (isPanel) {
|
val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position ->
|
||||||
arrayOf(
|
getItemId(position) != OPEN_APP_ID || openAppIntent != null
|
||||||
context.resources.getString(addControlsId),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
arrayOf(
|
|
||||||
context.resources.getString(addControlsId),
|
|
||||||
context.resources.getString(R.string.controls_menu_edit)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
|
|
||||||
|
|
||||||
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
|
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
|
||||||
anchor.setOnClickListener(object : View.OnClickListener {
|
anchor.setOnClickListener(object : View.OnClickListener {
|
||||||
@@ -462,25 +519,21 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
pos: Int,
|
pos: Int,
|
||||||
id: Long
|
id: Long
|
||||||
) {
|
) {
|
||||||
when (pos) {
|
when (id) {
|
||||||
// 0: Add Control
|
OPEN_APP_ID -> startDefaultActivity()
|
||||||
0 -> {
|
ADD_APP_ID -> startProviderSelectorActivity()
|
||||||
if (isPanel || newFlows) {
|
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
|
||||||
startProviderSelectorActivity()
|
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
|
||||||
} else {
|
|
||||||
startFavoritingActivity(selectedStructure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 1: Edit controls
|
|
||||||
1 -> startEditingActivity(selectedStructure)
|
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
show()
|
show()
|
||||||
|
listView?.post { listView?.requestAccessibilityFocus() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
overflowMenuAdapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
|
private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
|
||||||
@@ -542,6 +595,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
show()
|
show()
|
||||||
|
listView?.post { listView?.requestAccessibilityFocus() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -631,7 +685,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
|
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
|
||||||
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
|
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
|
||||||
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
|
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
|
||||||
.commit()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
|
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.widget.ArrayAdapter
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
|
||||||
|
open class OverflowMenuAdapter(
|
||||||
|
context: Context,
|
||||||
|
@LayoutRes layoutId: Int,
|
||||||
|
itemsWithIds: List<MenuItem>,
|
||||||
|
private val isEnabledInternal: OverflowMenuAdapter.(Int) -> Boolean
|
||||||
|
) : ArrayAdapter<CharSequence>(context, layoutId, itemsWithIds.map(MenuItem::text)) {
|
||||||
|
|
||||||
|
private val ids = itemsWithIds.map(MenuItem::id)
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return ids[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEnabled(position: Int): Boolean {
|
||||||
|
return isEnabledInternal(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MenuItem(val text: CharSequence, val id: Long)
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import android.app.PendingIntent
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.service.controls.ControlsProviderService
|
import android.service.controls.ControlsProviderService
|
||||||
@@ -95,6 +96,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var dumpManager: DumpManager
|
@Mock lateinit var dumpManager: DumpManager
|
||||||
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||||
@Mock lateinit var featureFlags: FeatureFlags
|
@Mock lateinit var featureFlags: FeatureFlags
|
||||||
|
@Mock lateinit var packageManager: PackageManager
|
||||||
val sharedPreferences = FakeSharedPreferences()
|
val sharedPreferences = FakeSharedPreferences()
|
||||||
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
ControlsUiControllerImpl(
|
ControlsUiControllerImpl(
|
||||||
Lazy { controlsController },
|
Lazy { controlsController },
|
||||||
context,
|
context,
|
||||||
|
packageManager,
|
||||||
uiExecutor,
|
uiExecutor,
|
||||||
bgExecutor,
|
bgExecutor,
|
||||||
Lazy { controlsListingController },
|
Lazy { controlsListingController },
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class OverflowMenuAdapterTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetItemId() {
|
||||||
|
val ids = listOf(27L, 73L)
|
||||||
|
val labels = listOf("first", "second")
|
||||||
|
val adapter =
|
||||||
|
OverflowMenuAdapter(
|
||||||
|
context,
|
||||||
|
layoutId = 0,
|
||||||
|
labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
|
||||||
|
) { true }
|
||||||
|
|
||||||
|
ids.forEachIndexed { index, id -> assertThat(adapter.getItemId(index)).isEqualTo(id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCheckEnabled() {
|
||||||
|
val ids = listOf(27L, 73L)
|
||||||
|
val labels = listOf("first", "second")
|
||||||
|
val adapter =
|
||||||
|
OverflowMenuAdapter(
|
||||||
|
context,
|
||||||
|
layoutId = 0,
|
||||||
|
labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
|
||||||
|
) { position -> position == 0 }
|
||||||
|
|
||||||
|
assertThat(adapter.isEnabled(0)).isTrue()
|
||||||
|
assertThat(adapter.isEnabled(1)).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user