Add dialog for recommended controls
The dialog can only be requested if the package of the controls provider is currently in the foreground. This is accomplished by querying Activity Manager about the UidImportance of that package. Added android.permission.PACKAGE_USAGE_STATS to SystemUI for this. Test: atest Test: manual Fixes: 149410221 Change-Id: Ifdf479d8dbc70502da95d362e3bfd60ad3c561fb
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
|
||||
<permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
|
||||
<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_FRAME_BUFFER"/>
|
||||
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
|
||||
|
||||
@@ -180,6 +180,8 @@
|
||||
|
||||
<!-- Adding Controls to SystemUI -->
|
||||
<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 -->
|
||||
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
|
||||
@@ -686,6 +688,25 @@
|
||||
android:visibleToInstantApps="true">
|
||||
</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 -->
|
||||
<service
|
||||
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_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 -->
|
||||
<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>
|
||||
<!-- Controls management controls screen header for Other zone [CHAR LIMIT=60] -->
|
||||
<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>
|
||||
|
||||
@@ -697,4 +697,7 @@
|
||||
<!-- used to override dark/light theming -->
|
||||
<item name="*android:colorPopupBackground">@color/control_list_popup_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.ControlsRequestDialog" parent="@style/Theme.SystemUI.MediaProjectionAlertDialog"/>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -123,6 +123,18 @@ interface ControlsController : UserAwareController {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -300,6 +300,19 @@ class ControlsControllerImpl @Inject constructor (
|
||||
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) {
|
||||
if (!confirmAvailability()) return
|
||||
executor.execute {
|
||||
@@ -437,6 +450,24 @@ private object Favorites {
|
||||
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) {
|
||||
val newFavMap = favMap.toMutableMap()
|
||||
val structures = mutableListOf<StructureInfo>()
|
||||
@@ -456,8 +487,8 @@ private object Favorites {
|
||||
structures.add(updatedStructure)
|
||||
}
|
||||
|
||||
newFavMap.put(componentName, structures.toList())
|
||||
favMap = newFavMap.toMap()
|
||||
newFavMap.put(componentName, structures)
|
||||
favMap = newFavMap
|
||||
}
|
||||
|
||||
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.ControlsListingControllerImpl
|
||||
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.ControlsUiControllerImpl
|
||||
import dagger.Binds
|
||||
@@ -69,4 +70,11 @@ abstract class ControlsModule {
|
||||
abstract fun provideControlsFavoritingActivity(
|
||||
activity: ControlsFavoritingActivity
|
||||
): 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
|
||||
*/
|
||||
class ControlAdapter(
|
||||
private val layoutInflater: LayoutInflater
|
||||
private val layoutInflater: LayoutInflater,
|
||||
private val elevation: Float
|
||||
) : RecyclerView.Adapter<Holder>() {
|
||||
|
||||
companion object {
|
||||
@@ -66,7 +67,7 @@ class ControlAdapter(
|
||||
layoutParams.apply {
|
||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
elevation = 15f
|
||||
elevation = this@ControlAdapter.elevation
|
||||
}
|
||||
) { id, favorite ->
|
||||
model?.changeFavoriteStatus(id, favorite)
|
||||
|
||||
@@ -142,8 +142,9 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
|
||||
val itemDecorator = MarginItemDecorator(margin, margin)
|
||||
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 {
|
||||
adapter = adapterAll
|
||||
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