Merge "Add Open app in overflow menu" into tm-qpr-dev

This commit is contained in:
Fabian Kozynski
2023-01-26 18:24:30 +00:00
committed by Android (Google) Code Review
5 changed files with 189 additions and 31 deletions

View File

@@ -189,9 +189,9 @@ open class ControlsProviderSelectorActivity @Inject constructor(
authorizedPanelsRepository.addAuthorizedPanels(
setOf(serviceInfo.componentName.packageName)
)
animateExitAndFinish()
val selected = SelectedItem.PanelItem(appName, componentName)
controlsController.setPreferredSelection(selected)
animateExitAndFinish()
openControlsOrigin()
}
dialog = null

View File

@@ -25,6 +25,7 @@ import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -38,6 +39,7 @@ import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.BaseAdapter
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -90,6 +92,7 @@ private data class ControlKey(val componentName: ComponentName, val controlId: S
class ControlsUiControllerImpl @Inject constructor (
val controlsController: Lazy<ControlsController>,
val context: Context,
private val packageManager: PackageManager,
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
@@ -113,6 +116,11 @@ class ControlsUiControllerImpl @Inject constructor (
private const val PREF_IS_PANEL = "controls_is_panel"
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
@@ -140,6 +148,9 @@ class ControlsUiControllerImpl @Inject constructor (
it.getTitle()
}
private var openAppIntent: Intent? = null
private var overflowMenuAdapter: BaseAdapter? = null
private val onSeedingComplete = Consumer<Boolean> {
accepted ->
if (accepted) {
@@ -216,6 +227,8 @@ class ControlsUiControllerImpl @Inject constructor (
this.parent = parent
this.onDismiss = onDismiss
this.activityContext = activityContext
this.openAppIntent = null
this.overflowMenuAdapter = null
hidden = false
retainCache = false
@@ -306,6 +319,12 @@ class ControlsUiControllerImpl @Inject constructor (
startTargetedActivity(si, ControlsEditingActivity::class.java)
}
private fun startDefaultActivity() {
openAppIntent?.let {
startActivity(it, animateExtra = false)
}
}
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
val i = Intent(activityContext, klazz)
putIntentExtras(i, si)
@@ -329,9 +348,11 @@ class ControlsUiControllerImpl @Inject constructor (
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
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
if (animateExtra) {
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
}
if (keyguardStateController.isShowing()) {
activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
@@ -383,8 +404,31 @@ class ControlsUiControllerImpl @Inject constructor (
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)
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) {
@@ -423,28 +467,41 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
private fun createMenu() {
private fun createMenu(extraApps: Boolean) {
val isPanel = selectedItem is SelectedItem.PanelItem
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
?: EMPTY_STRUCTURE
val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
val addControlsId = if (newFlows || isPanel) {
R.string.controls_menu_add_another_app
} else {
R.string.controls_menu_add
val items = buildList {
add(OverflowMenuAdapter.MenuItem(
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) {
arrayOf(
context.resources.getString(addControlsId),
)
} else {
arrayOf(
context.resources.getString(addControlsId),
context.resources.getString(R.string.controls_menu_edit)
)
val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position ->
getItemId(position) != OPEN_APP_ID || openAppIntent != null
}
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
anchor.setOnClickListener(object : View.OnClickListener {
@@ -462,25 +519,21 @@ class ControlsUiControllerImpl @Inject constructor (
pos: Int,
id: Long
) {
when (pos) {
// 0: Add Control
0 -> {
if (isPanel || newFlows) {
startProviderSelectorActivity()
} else {
startFavoritingActivity(selectedStructure)
}
}
// 1: Edit controls
1 -> startEditingActivity(selectedStructure)
when (id) {
OPEN_APP_ID -> startDefaultActivity()
ADD_APP_ID -> startProviderSelectorActivity()
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
}
dismiss()
}
})
show()
listView?.post { listView?.requestAccessibilityFocus() }
}
}
})
overflowMenuAdapter = adapter
}
private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
@@ -542,6 +595,7 @@ class ControlsUiControllerImpl @Inject constructor (
}
})
show()
listView?.post { listView?.requestAccessibilityFocus() }
}
}
})
@@ -631,7 +685,7 @@ class ControlsUiControllerImpl @Inject constructor (
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
.commit()
.apply()
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {

View File

@@ -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)
}

View File

@@ -20,6 +20,7 @@ import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
@@ -95,6 +96,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
@Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var packageManager: PackageManager
val sharedPreferences = FakeSharedPreferences()
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@@ -124,6 +126,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
ControlsUiControllerImpl(
Lazy { controlsController },
context,
packageManager,
uiExecutor,
bgExecutor,
Lazy { controlsListingController },

View File

@@ -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()
}
}