Merge "Add dialog for recommended controls" into rvc-dev am: 6bb7ecf725 am: b12e9d610b am: 003be1a73e
Change-Id: I9f93cbb8ea4877eccfb809aefcbd0d51144951d4
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
|
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
|
||||||
<permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
|
<permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
|
||||||
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
|
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
|
||||||
|
<permission name="android.permission.PACKAGE_USAGE_STATS" />
|
||||||
<permission name="android.permission.READ_DREAM_STATE"/>
|
<permission name="android.permission.READ_DREAM_STATE"/>
|
||||||
<permission name="android.permission.READ_FRAME_BUFFER"/>
|
<permission name="android.permission.READ_FRAME_BUFFER"/>
|
||||||
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
|
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
|
||||||
|
|||||||
@@ -180,6 +180,8 @@
|
|||||||
|
|
||||||
<!-- Adding Controls to SystemUI -->
|
<!-- Adding Controls to SystemUI -->
|
||||||
<uses-permission android:name="android.permission.BIND_CONTROLS" />
|
<uses-permission android:name="android.permission.BIND_CONTROLS" />
|
||||||
|
<!-- Check foreground controls applications -->
|
||||||
|
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
|
||||||
|
|
||||||
<!-- Quick Settings tile: Night Mode / Dark Theme -->
|
<!-- Quick Settings tile: Night Mode / Dark Theme -->
|
||||||
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
|
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
|
||||||
@@ -686,6 +688,25 @@
|
|||||||
android:visibleToInstantApps="true">
|
android:visibleToInstantApps="true">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<receiver android:name=".controls.management.ControlsRequestReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.controls.action.ADD_CONTROL" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- started from ControlsFavoritingActivity -->
|
||||||
|
<activity
|
||||||
|
android:name=".controls.management.ControlsRequestDialog"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.ControlsRequestDialog"
|
||||||
|
android:finishOnCloseSystemDialogs="true"
|
||||||
|
android:showForAllUsers="true"
|
||||||
|
android:clearTaskOnLaunch="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:visibleToInstantApps="true"/>
|
||||||
|
|
||||||
<!-- Doze with notifications, run in main sysui process for every user -->
|
<!-- Doze with notifications, run in main sysui process for every user -->
|
||||||
<service
|
<service
|
||||||
android:name=".doze.DozeService"
|
android:name=".doze.DozeService"
|
||||||
|
|||||||
34
packages/SystemUI/res/layout/controls_dialog.xml
Normal file
34
packages/SystemUI/res/layout/controls_dialog.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/controls_dialog_padding"
|
||||||
|
android:layout_margin="@dimen/controls_dialog_padding"
|
||||||
|
>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/control"
|
||||||
|
layout="@layout/controls_base_item"
|
||||||
|
android:layout_width="@dimen/controls_dialog_control_width"
|
||||||
|
android:layout_height="@dimen/control_height"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="@dimen/controls_dialog_padding"
|
||||||
|
android:layout_marginBottom="@dimen/controls_dialog_padding"
|
||||||
|
/>
|
||||||
|
</FrameLayout>
|
||||||
@@ -1249,6 +1249,10 @@
|
|||||||
<dimen name="controls_app_divider_side_margin">32dp</dimen>
|
<dimen name="controls_app_divider_side_margin">32dp</dimen>
|
||||||
|
|
||||||
<dimen name="controls_card_margin">2dp</dimen>
|
<dimen name="controls_card_margin">2dp</dimen>
|
||||||
|
<item name="control_card_elevation" type="dimen" format="float">15</item>
|
||||||
|
|
||||||
|
<dimen name="controls_dialog_padding">8dp</dimen>
|
||||||
|
<dimen name="controls_dialog_control_width">200dp</dimen>
|
||||||
|
|
||||||
<!-- Screen Record -->
|
<!-- Screen Record -->
|
||||||
<dimen name="screenrecord_dialog_padding">18dp</dimen>
|
<dimen name="screenrecord_dialog_padding">18dp</dimen>
|
||||||
|
|||||||
@@ -2639,4 +2639,11 @@
|
|||||||
<string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
|
<string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
|
||||||
<!-- Controls management controls screen header for Other zone [CHAR LIMIT=60] -->
|
<!-- Controls management controls screen header for Other zone [CHAR LIMIT=60] -->
|
||||||
<string name="controls_favorite_other_zone_header">Other</string>
|
<string name="controls_favorite_other_zone_header">Other</string>
|
||||||
|
|
||||||
|
<!-- Controls dialog title [CHAR LIMIT=30] -->
|
||||||
|
<string name="controls_dialog_title">Add to Quick Controls</string>
|
||||||
|
<!-- Controls dialog add to favorites [CHAR LIMIT=30] -->
|
||||||
|
<string name="controls_dialog_ok">Add to favorites</string>
|
||||||
|
<!-- Controls dialog message [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="controls_dialog_message"><xliff:g id="app" example="System UI">%s</xliff:g> suggested this control to add to your favorites.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -697,4 +697,7 @@
|
|||||||
<!-- used to override dark/light theming -->
|
<!-- used to override dark/light theming -->
|
||||||
<item name="*android:colorPopupBackground">@color/control_list_popup_background</item>
|
<item name="*android:colorPopupBackground">@color/control_list_popup_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.ControlsRequestDialog" parent="@style/Theme.SystemUI.MediaProjectionAlertDialog"/>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -123,6 +123,18 @@ interface ControlsController : UserAwareController {
|
|||||||
*/
|
*/
|
||||||
fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo>
|
fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single favorite to a given component and structure
|
||||||
|
* @param componentName the name of the service that provides the [Control]
|
||||||
|
* @param structureName the name of the structure that holds the [Control]
|
||||||
|
* @param controlInfo persistent information about the [Control] to be added.
|
||||||
|
*/
|
||||||
|
fun addFavorite(
|
||||||
|
componentName: ComponentName,
|
||||||
|
structureName: CharSequence,
|
||||||
|
controlInfo: ControlInfo
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the favorites for the given structure.
|
* Replaces the favorites for the given structure.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -300,6 +300,19 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
bindingController.unsubscribe()
|
bindingController.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addFavorite(
|
||||||
|
componentName: ComponentName,
|
||||||
|
structureName: CharSequence,
|
||||||
|
controlInfo: ControlInfo
|
||||||
|
) {
|
||||||
|
if (!confirmAvailability()) return
|
||||||
|
executor.execute {
|
||||||
|
if (Favorites.addFavorite(componentName, structureName, controlInfo)) {
|
||||||
|
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun replaceFavoritesForStructure(structureInfo: StructureInfo) {
|
override fun replaceFavoritesForStructure(structureInfo: StructureInfo) {
|
||||||
if (!confirmAvailability()) return
|
if (!confirmAvailability()) return
|
||||||
executor.execute {
|
executor.execute {
|
||||||
@@ -437,6 +450,24 @@ private object Favorites {
|
|||||||
favMap = newFavMap
|
favMap = newFavMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFavorite(
|
||||||
|
componentName: ComponentName,
|
||||||
|
structureName: CharSequence,
|
||||||
|
controlInfo: ControlInfo
|
||||||
|
): Boolean {
|
||||||
|
// Check if control is in favorites
|
||||||
|
if (getControlsForComponent(componentName)
|
||||||
|
.any { it.controlId == controlInfo.controlId }) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val structureInfo = favMap.get(componentName)
|
||||||
|
?.firstOrNull { it.structure == structureName }
|
||||||
|
?: StructureInfo(componentName, structureName, emptyList())
|
||||||
|
val newStructureInfo = structureInfo.copy(controls = structureInfo.controls + controlInfo)
|
||||||
|
replaceControls(newStructureInfo)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun replaceControls(updatedStructure: StructureInfo) {
|
fun replaceControls(updatedStructure: StructureInfo) {
|
||||||
val newFavMap = favMap.toMutableMap()
|
val newFavMap = favMap.toMutableMap()
|
||||||
val structures = mutableListOf<StructureInfo>()
|
val structures = mutableListOf<StructureInfo>()
|
||||||
@@ -456,8 +487,8 @@ private object Favorites {
|
|||||||
structures.add(updatedStructure)
|
structures.add(updatedStructure)
|
||||||
}
|
}
|
||||||
|
|
||||||
newFavMap.put(componentName, structures.toList())
|
newFavMap.put(componentName, structures)
|
||||||
favMap = newFavMap.toMap()
|
favMap = newFavMap
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.android.systemui.controls.management.ControlsFavoritingActivity
|
|||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.controls.management.ControlsListingControllerImpl
|
import com.android.systemui.controls.management.ControlsListingControllerImpl
|
||||||
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||||
|
import com.android.systemui.controls.management.ControlsRequestDialog
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
import com.android.systemui.controls.ui.ControlsUiControllerImpl
|
import com.android.systemui.controls.ui.ControlsUiControllerImpl
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
@@ -69,4 +70,11 @@ abstract class ControlsModule {
|
|||||||
abstract fun provideControlsFavoritingActivity(
|
abstract fun provideControlsFavoritingActivity(
|
||||||
activity: ControlsFavoritingActivity
|
activity: ControlsFavoritingActivity
|
||||||
): Activity
|
): Activity
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(ControlsRequestDialog::class)
|
||||||
|
abstract fun provideControlsRequestDialog(
|
||||||
|
activity: ControlsRequestDialog
|
||||||
|
): Activity
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
|
|||||||
* @param onlyFavorites set to true to only display favorites instead of all controls
|
* @param onlyFavorites set to true to only display favorites instead of all controls
|
||||||
*/
|
*/
|
||||||
class ControlAdapter(
|
class ControlAdapter(
|
||||||
private val layoutInflater: LayoutInflater
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val elevation: Float
|
||||||
) : RecyclerView.Adapter<Holder>() {
|
) : RecyclerView.Adapter<Holder>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -66,7 +67,7 @@ class ControlAdapter(
|
|||||||
layoutParams.apply {
|
layoutParams.apply {
|
||||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
elevation = 15f
|
elevation = this@ControlAdapter.elevation
|
||||||
}
|
}
|
||||||
) { id, favorite ->
|
) { id, favorite ->
|
||||||
model?.changeFavoriteStatus(id, favorite)
|
model?.changeFavoriteStatus(id, favorite)
|
||||||
|
|||||||
@@ -142,8 +142,9 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
|
val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
|
||||||
val itemDecorator = MarginItemDecorator(margin, margin)
|
val itemDecorator = MarginItemDecorator(margin, margin)
|
||||||
val layoutInflater = LayoutInflater.from(applicationContext)
|
val layoutInflater = LayoutInflater.from(applicationContext)
|
||||||
|
val elevation = resources.getFloat(R.dimen.control_card_elevation)
|
||||||
|
|
||||||
adapterAll = ControlAdapter(layoutInflater)
|
adapterAll = ControlAdapter(layoutInflater, elevation)
|
||||||
recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply {
|
recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply {
|
||||||
adapter = adapterAll
|
adapter = adapterAll
|
||||||
layoutManager = GridLayoutManager(applicationContext, 2).apply {
|
layoutManager = GridLayoutManager(applicationContext, 2).apply {
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* 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.management
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.service.controls.Control
|
||||||
|
import android.service.controls.ControlsProviderService
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
|
import com.android.systemui.controls.controller.ControlInfo
|
||||||
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
|
import com.android.systemui.controls.ui.RenderInfo
|
||||||
|
import com.android.systemui.settings.CurrentUserTracker
|
||||||
|
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||||
|
import com.android.systemui.util.LifecycleActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ControlsRequestDialog @Inject constructor(
|
||||||
|
private val controller: ControlsController,
|
||||||
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
|
private val controlsListingController: ControlsListingController
|
||||||
|
) : LifecycleActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ControlsRequestDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var component: ComponentName
|
||||||
|
private lateinit var control: Control
|
||||||
|
private var dialog: Dialog? = null
|
||||||
|
private val callback = object : ControlsListingController.ControlsListingCallback {
|
||||||
|
override fun onServicesUpdated(candidates: List<ControlsServiceInfo>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||||
|
private val startingUser = controller.currentUserId
|
||||||
|
|
||||||
|
override fun onUserSwitched(newUserId: Int) {
|
||||||
|
if (newUserId != startingUser) {
|
||||||
|
stopTracking()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (!controller.available) {
|
||||||
|
Log.w(TAG, "Quick Controls not available for this user ")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
currentUserTracker.startTracking()
|
||||||
|
controlsListingController.addCallback(callback)
|
||||||
|
|
||||||
|
val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
|
||||||
|
val currentUser = controller.currentUserId
|
||||||
|
|
||||||
|
if (requestUser != currentUser) {
|
||||||
|
Log.w(TAG, "Current user ($currentUser) different from request user ($requestUser)")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
component = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME) ?: run {
|
||||||
|
Log.e(TAG, "Request did not contain componentName")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
control = intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL) ?: run {
|
||||||
|
Log.e(TAG, "Request did not contain control")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
val label = verifyComponentAndGetLabel()
|
||||||
|
if (label == null) {
|
||||||
|
Log.e(TAG, "The component specified (${component.flattenToString()} " +
|
||||||
|
"is not a valid ControlsProviderService")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCurrentFavorite()) {
|
||||||
|
Log.w(TAG, "The control ${control.title} is already a favorite")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog = createDialog(label)
|
||||||
|
|
||||||
|
dialog?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
dialog?.dismiss()
|
||||||
|
currentUserTracker.stopTracking()
|
||||||
|
controlsListingController.removeCallback(callback)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyComponentAndGetLabel(): CharSequence? {
|
||||||
|
return controlsListingController.getAppLabel(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCurrentFavorite(): Boolean {
|
||||||
|
val favorites = controller.getFavoritesForComponent(component)
|
||||||
|
return favorites.any { it.controls.any { it.controlId == control.controlId } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDialog(label: CharSequence): Dialog {
|
||||||
|
|
||||||
|
val renderInfo = RenderInfo.lookup(control.deviceType, true)
|
||||||
|
val frame = LayoutInflater.from(this).inflate(R.layout.controls_dialog, null).apply {
|
||||||
|
requireViewById<ImageView>(R.id.icon).apply {
|
||||||
|
setImageIcon(Icon.createWithResource(context, renderInfo.iconResourceId))
|
||||||
|
setImageTintList(
|
||||||
|
context.resources.getColorStateList(renderInfo.foreground, context.theme))
|
||||||
|
}
|
||||||
|
requireViewById<TextView>(R.id.title).text = control.title
|
||||||
|
requireViewById<TextView>(R.id.subtitle).text = control.subtitle
|
||||||
|
requireViewById<View>(R.id.control).elevation =
|
||||||
|
resources.getFloat(R.dimen.control_card_elevation)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setTitle(getString(R.string.controls_dialog_title))
|
||||||
|
.setMessage(getString(R.string.controls_dialog_message, label))
|
||||||
|
.setPositiveButton(R.string.controls_dialog_ok, this)
|
||||||
|
.setNegativeButton(android.R.string.cancel, this)
|
||||||
|
.setOnCancelListener(this)
|
||||||
|
.setView(frame)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
SystemUIDialog.registerDismissListener(dialog)
|
||||||
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(dialog: DialogInterface?) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||||
|
if (which == Dialog.BUTTON_POSITIVE) {
|
||||||
|
controller.addFavorite(componentName, control.structure ?: "",
|
||||||
|
ControlInfo(control.controlId, control.title, control.deviceType))
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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.management
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.service.controls.Control
|
||||||
|
import android.service.controls.ControlsProviderService
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy to launch in user 0
|
||||||
|
*/
|
||||||
|
class ControlsRequestReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ControlsRequestReceiver"
|
||||||
|
|
||||||
|
fun isPackageInForeground(context: Context, packageName: String): Boolean {
|
||||||
|
val uid = try {
|
||||||
|
context.packageManager.getPackageUid(packageName, 0)
|
||||||
|
} catch (_: PackageManager.NameNotFoundException) {
|
||||||
|
Log.w(TAG, "Package $packageName not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val am = context.getSystemService(ActivityManager::class.java)
|
||||||
|
if ((am?.getUidImportance(uid) ?: IMPORTANCE_GONE) != IMPORTANCE_FOREGROUND) {
|
||||||
|
Log.w(TAG, "Uid $uid not in foreground")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
|
||||||
|
val packageName = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
|
||||||
|
?.packageName
|
||||||
|
|
||||||
|
if (packageName == null || !isPackageInForeground(context, packageName)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val activityIntent = Intent(context, ControlsRequestDialog::class.java).apply {
|
||||||
|
Intent.EXTRA_COMPONENT_NAME.let {
|
||||||
|
putExtra(it, intent.getParcelableExtra<ComponentName>(it))
|
||||||
|
}
|
||||||
|
ControlsProviderService.EXTRA_CONTROL.let {
|
||||||
|
putExtra(it, intent.getParcelableExtra<Control>(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activityIntent.putExtra(Intent.EXTRA_USER_ID, context.userId)
|
||||||
|
|
||||||
|
context.startActivityAsUser(activityIntent, UserHandle.SYSTEM)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* 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.management
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.service.controls.Control
|
||||||
|
import android.service.controls.ControlsProviderService
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
import org.mockito.ArgumentMatchers.eq
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class ControlsRequestReceiverTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var packageManager: PackageManager
|
||||||
|
@Mock
|
||||||
|
private lateinit var activityManager: ActivityManager
|
||||||
|
@Mock
|
||||||
|
private lateinit var control: Control
|
||||||
|
|
||||||
|
private val componentName = ComponentName("test_pkg", "test_cls")
|
||||||
|
private lateinit var receiver: ControlsRequestReceiver
|
||||||
|
private lateinit var wrapper: MyWrapper
|
||||||
|
private lateinit var intent: Intent
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
mContext.setMockPackageManager(packageManager)
|
||||||
|
mContext.addMockSystemService(ActivityManager::class.java, activityManager)
|
||||||
|
|
||||||
|
receiver = ControlsRequestReceiver()
|
||||||
|
|
||||||
|
wrapper = MyWrapper(context)
|
||||||
|
|
||||||
|
intent = Intent(ControlsProviderService.ACTION_ADD_CONTROL).apply {
|
||||||
|
putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
|
||||||
|
putExtra(ControlsProviderService.EXTRA_CONTROL, control)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPackageVerification_nonExistentPackage() {
|
||||||
|
`when`(packageManager.getPackageUid(anyString(), anyInt()))
|
||||||
|
.thenThrow(PackageManager.NameNotFoundException::class.java)
|
||||||
|
|
||||||
|
assertFalse(ControlsRequestReceiver.isPackageInForeground(mContext, "TEST"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPackageVerification_uidNotInForeground() {
|
||||||
|
`when`(packageManager.getPackageUid(anyString(), anyInt())).thenReturn(12345)
|
||||||
|
|
||||||
|
`when`(activityManager.getUidImportance(anyInt())).thenReturn(IMPORTANCE_GONE)
|
||||||
|
|
||||||
|
assertFalse(ControlsRequestReceiver.isPackageInForeground(mContext, "TEST"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPackageVerification_OK() {
|
||||||
|
`when`(packageManager.getPackageUid(anyString(), anyInt())).thenReturn(12345)
|
||||||
|
|
||||||
|
`when`(activityManager.getUidImportance(anyInt())).thenReturn(IMPORTANCE_GONE)
|
||||||
|
`when`(activityManager.getUidImportance(12345)).thenReturn(IMPORTANCE_FOREGROUND)
|
||||||
|
|
||||||
|
assertTrue(ControlsRequestReceiver.isPackageInForeground(mContext, "TEST"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnReceive_packageNotVerified_nameNotFound() {
|
||||||
|
`when`(packageManager.getPackageUid(eq(componentName.packageName), anyInt()))
|
||||||
|
.thenThrow(PackageManager.NameNotFoundException::class.java)
|
||||||
|
|
||||||
|
receiver.onReceive(wrapper, intent)
|
||||||
|
|
||||||
|
assertNull(wrapper.intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnReceive_packageNotVerified_notForeground() {
|
||||||
|
`when`(packageManager.getPackageUid(eq(componentName.packageName), anyInt()))
|
||||||
|
.thenReturn(12345)
|
||||||
|
|
||||||
|
`when`(activityManager.getUidImportance(anyInt())).thenReturn(IMPORTANCE_GONE)
|
||||||
|
|
||||||
|
receiver.onReceive(wrapper, intent)
|
||||||
|
|
||||||
|
assertNull(wrapper.intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnReceive_OK() {
|
||||||
|
`when`(packageManager.getPackageUid(eq(componentName.packageName), anyInt()))
|
||||||
|
.thenReturn(12345)
|
||||||
|
|
||||||
|
`when`(activityManager.getUidImportance(eq(12345))).thenReturn(IMPORTANCE_FOREGROUND)
|
||||||
|
|
||||||
|
receiver.onReceive(wrapper, intent)
|
||||||
|
|
||||||
|
wrapper.intent?.let {
|
||||||
|
assertEquals(ComponentName(wrapper, ControlsRequestDialog::class.java), it.component)
|
||||||
|
|
||||||
|
assertEquals(control, it.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL))
|
||||||
|
|
||||||
|
assertEquals(componentName, it.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME))
|
||||||
|
} ?: run { fail("Null start intent") }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyWrapper(context: Context) : ContextWrapper(context) {
|
||||||
|
var intent: Intent? = null
|
||||||
|
|
||||||
|
override fun startActivityAsUser(intent: Intent, user: UserHandle) {
|
||||||
|
// Always launch activity as system
|
||||||
|
assertTrue(user == UserHandle.SYSTEM)
|
||||||
|
this.intent = intent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startActivity(intent: Intent) {
|
||||||
|
this.intent = intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user