Merge "Add dialog for recommended controls" into rvc-dev am: 6bb7ecf725
Change-Id: I5ec280894d69ba4909e317c84a6dd7fe67561d2d
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