Add dialog and controller for TileServiceRequest
This adds the SystemUI side. As there's no API, it's triggered using CommandRegistry and outputs response to logcat. The design of the dialog is not final, so measures and styles will be changed in a future CL. Test: atest TileRequestDialogTest TileServiceRequestControllerTest Test: manual Fixes: 196056024 Change-Id: I5a52b7cd9db6a7fc79a8751e7702be8c005b8def
This commit is contained in:
@@ -60,6 +60,10 @@ public abstract class QSTileView extends LinearLayout {
|
||||
|
||||
public abstract int getDetailY();
|
||||
|
||||
public View getLabel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public View getLabelContainer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
36
packages/SystemUI/res/layout/tile_service_request_dialog.xml
Normal file
36
packages/SystemUI/res/layout/tile_service_request_dialog.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textDirection="locale"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.PrivacyDialog"
|
||||
android:lineHeight="20sp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
@@ -1636,4 +1636,8 @@
|
||||
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
|
||||
|
||||
<dimen name="drag_and_drop_icon_size">70dp</dimen>
|
||||
|
||||
<dimen name="qs_tile_service_request_dialog_width">304dp</dimen>
|
||||
<dimen name="qs_tile_service_request_tile_width">192dp</dimen>
|
||||
<dimen name="qs_tile_service_request_content_space">24dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -3022,4 +3022,12 @@
|
||||
<string name="wifi_failed_connect_message">Failed to connect to network</string>
|
||||
<!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
|
||||
<string name="see_all_networks">See all</string>
|
||||
|
||||
<!-- Text for TileService request dialog. This is shown to the user that an app is requesting
|
||||
user approval to add the shown tile to Quick Settings [CHAR LIMIT=NONE] -->
|
||||
<string name="qs_tile_request_dialog_text"><xliff:g id="appName" example="Fake App">%1$s</xliff:g> wants to add the following tile to Quick Settings</string>
|
||||
<!-- Text for TileService request dialog. Text for button for user to approve adding the tile [CHAR LIMIT=20] -->
|
||||
<string name="qs_tile_request_dialog_add">Add tile</string>
|
||||
<!-- Text for TileService request dialog. Text for button for user to reject adding the tile [CHAR LIMIT=20] -->
|
||||
<string name="qs_tile_request_dialog_not_add">Do not add tile</string>
|
||||
</resources>
|
||||
|
||||
@@ -727,6 +727,16 @@
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
</style>
|
||||
|
||||
<!-- TileService request dialog -->
|
||||
<style name="TileRequestDialog" parent="Theme.SystemUI.QuickSettings.Dialog">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@drawable/qs_dialog_bg</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
</style>
|
||||
|
||||
<!-- USB Contaminant dialog -->
|
||||
<style name ="USBContaminant" />
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import com.android.systemui.qs.external.CustomTile;
|
||||
import com.android.systemui.qs.external.CustomTileStatePersister;
|
||||
import com.android.systemui.qs.external.TileLifecycleManager;
|
||||
import com.android.systemui.qs.external.TileServiceKey;
|
||||
import com.android.systemui.qs.external.TileServiceRequestController;
|
||||
import com.android.systemui.qs.external.TileServices;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
@@ -106,6 +107,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
private UserTracker mUserTracker;
|
||||
private SecureSettings mSecureSettings;
|
||||
|
||||
private final TileServiceRequestController mTileServiceRequestController;
|
||||
|
||||
@Inject
|
||||
public QSTileHost(Context context,
|
||||
StatusBarIconController iconController,
|
||||
@@ -122,7 +125,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
UiEventLogger uiEventLogger,
|
||||
UserTracker userTracker,
|
||||
SecureSettings secureSettings,
|
||||
CustomTileStatePersister customTileStatePersister
|
||||
CustomTileStatePersister customTileStatePersister,
|
||||
TileServiceRequestController.Builder tileServiceRequestControllerBuilder
|
||||
) {
|
||||
mIconController = iconController;
|
||||
mContext = context;
|
||||
@@ -133,6 +137,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
mQSLogger = qsLogger;
|
||||
mUiEventLogger = uiEventLogger;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
|
||||
|
||||
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
|
||||
mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker);
|
||||
@@ -152,6 +157,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
tunerService.addTunable(this, TILES_SETTING);
|
||||
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
|
||||
mAutoTiles = autoTiles.get();
|
||||
mTileServiceRequestController.init();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,6 +177,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
mServices.destroy();
|
||||
mPluginManager.removePluginListener(this);
|
||||
mDumpManager.unregisterDumpable(TAG);
|
||||
mTileServiceRequestController.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
110
packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
vendored
Normal file
110
packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.qs.external
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.WindowInsets
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.plugins.qs.QSTile
|
||||
import com.android.systemui.plugins.qs.QSTileView
|
||||
import com.android.systemui.qs.tileimpl.QSIconViewImpl
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
|
||||
import com.android.systemui.qs.tileimpl.QSTileViewImpl
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||
|
||||
/**
|
||||
* Dialog to present to the user to ask for authorization to add a [TileService].
|
||||
*/
|
||||
class TileRequestDialog(
|
||||
context: Context
|
||||
) : SystemUIDialog(context, R.style.TileRequestDialog) {
|
||||
|
||||
companion object {
|
||||
internal val CONTENT_ID = R.id.content
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window?.apply {
|
||||
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
|
||||
attributes.receiveInsetsIgnoringZOrder = true
|
||||
setLayout(
|
||||
context.resources
|
||||
.getDimensionPixelSize(R.dimen.qs_tile_service_request_dialog_width),
|
||||
WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data of the tile to add, to show the user.
|
||||
*/
|
||||
fun setTileData(tileData: TileData) {
|
||||
val ll = (LayoutInflater
|
||||
.from(context)
|
||||
.inflate(R.layout.tile_service_request_dialog, null)
|
||||
as ViewGroup).apply {
|
||||
requireViewById<TextView>(R.id.text).apply {
|
||||
text = context
|
||||
.getString(R.string.qs_tile_request_dialog_text, tileData.appName)
|
||||
}
|
||||
addView(
|
||||
createTileView(tileData),
|
||||
context.resources.getDimensionPixelSize(
|
||||
R.dimen.qs_tile_service_request_tile_width),
|
||||
context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
|
||||
)
|
||||
}
|
||||
val spacing = context.resources.getDimensionPixelSize(
|
||||
R.dimen.qs_tile_service_request_content_space
|
||||
)
|
||||
setView(ll, spacing, spacing, spacing, spacing / 2)
|
||||
}
|
||||
|
||||
private fun createTileView(tileData: TileData): QSTileView {
|
||||
val tile = QSTileViewImpl(context, QSIconViewImpl(context), true)
|
||||
val state = QSTile.BooleanState().apply {
|
||||
label = tileData.label
|
||||
icon = tileData.icon?.loadDrawable(context)?.let {
|
||||
QSTileImpl.DrawableIcon(it)
|
||||
} ?: ResourceIcon.get(R.drawable.android)
|
||||
}
|
||||
tile.onStateChanged(state)
|
||||
tile.isSelected = true
|
||||
return tile
|
||||
}
|
||||
|
||||
/**
|
||||
* Data bundle of information to show the user.
|
||||
*
|
||||
* @property appName Name of the app requesting their [TileService] to be added.
|
||||
* @property label Label of the tile.
|
||||
* @property icon Icon for the tile.
|
||||
*/
|
||||
data class TileData(
|
||||
val appName: CharSequence,
|
||||
val label: CharSequence,
|
||||
val icon: Icon?
|
||||
)
|
||||
}
|
||||
142
packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
vendored
Normal file
142
packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.qs.external
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ComponentName
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.Icon
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.qs.QSTileHost
|
||||
import com.android.systemui.statusbar.commandline.Command
|
||||
import com.android.systemui.statusbar.commandline.CommandRegistry
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||
import com.android.systemui.R
|
||||
import java.io.PrintWriter
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TileServiceRequestController"
|
||||
|
||||
/**
|
||||
* Controller to interface between [TileRequestDialog] and [QSTileHost].
|
||||
*/
|
||||
class TileServiceRequestController constructor(
|
||||
private val qsTileHost: QSTileHost,
|
||||
private val commandRegistry: CommandRegistry,
|
||||
private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsTileHost.context) }
|
||||
) {
|
||||
|
||||
companion object {
|
||||
// Temporary return values while there's no API
|
||||
internal const val ADD_TILE = 0
|
||||
internal const val DONT_ADD_TILE = 1
|
||||
internal const val TILE_ALREADY_ADDED = 2
|
||||
internal const val DISMISSED = 3
|
||||
}
|
||||
|
||||
fun init() {
|
||||
commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() }
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
commandRegistry.unregisterCommand("tile-service-add")
|
||||
}
|
||||
|
||||
private fun addTile(componentName: ComponentName) {
|
||||
qsTileHost.addTile(componentName, true)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun requestTileAdd(
|
||||
componentName: ComponentName,
|
||||
appName: CharSequence,
|
||||
label: CharSequence,
|
||||
icon: Icon?,
|
||||
callback: Consumer<Int>
|
||||
) {
|
||||
if (isTileAlreadyAdded(componentName)) {
|
||||
callback.accept(TILE_ALREADY_ADDED)
|
||||
return
|
||||
}
|
||||
val dialogResponse = object : Consumer<Int> {
|
||||
override fun accept(response: Int) {
|
||||
if (response == ADD_TILE) {
|
||||
addTile(componentName)
|
||||
}
|
||||
callback.accept(response)
|
||||
}
|
||||
}
|
||||
val tileData = TileRequestDialog.TileData(appName, label, icon)
|
||||
createDialog(tileData, dialogResponse).show()
|
||||
}
|
||||
|
||||
private fun createDialog(
|
||||
tileData: TileRequestDialog.TileData,
|
||||
responseHandler: Consumer<Int>
|
||||
): SystemUIDialog {
|
||||
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
|
||||
if (which == Dialog.BUTTON_POSITIVE) {
|
||||
responseHandler.accept(ADD_TILE)
|
||||
} else {
|
||||
responseHandler.accept(DONT_ADD_TILE)
|
||||
}
|
||||
}
|
||||
return dialogCreator().apply {
|
||||
setTileData(tileData)
|
||||
setShowForAllUsers(true)
|
||||
setCanceledOnTouchOutside(true)
|
||||
setOnCancelListener { responseHandler.accept(DISMISSED) }
|
||||
setPositiveButton(R.string.qs_tile_request_dialog_add, dialogClickListener)
|
||||
setNegativeButton(R.string.qs_tile_request_dialog_not_add, dialogClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTileAlreadyAdded(componentName: ComponentName): Boolean {
|
||||
val spec = CustomTile.toSpec(componentName)
|
||||
return qsTileHost.indexOf(spec) != -1
|
||||
}
|
||||
|
||||
inner class TileServiceRequestCommand : Command {
|
||||
override fun execute(pw: PrintWriter, args: List<String>) {
|
||||
val componentName: ComponentName = ComponentName.unflattenFromString(args[0])
|
||||
?: run {
|
||||
Log.w(TAG, "Malformed componentName ${args[0]}")
|
||||
return
|
||||
}
|
||||
requestTileAdd(componentName, args[1], args[2], null) {
|
||||
Log.d(TAG, "Response: $it")
|
||||
}
|
||||
}
|
||||
|
||||
override fun help(pw: PrintWriter) {
|
||||
pw.println("Usage: adb shell cmd statusbar tile-service-add " +
|
||||
"<componentName> <appName> <label>")
|
||||
}
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class Builder @Inject constructor(
|
||||
private val commandRegistry: CommandRegistry
|
||||
) {
|
||||
fun create(qsTileHost: QSTileHost): TileServiceRequestController {
|
||||
return TileServiceRequestController(qsTileHost, commandRegistry)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,6 +286,10 @@ open class QSTileViewImpl @JvmOverloads constructor(
|
||||
return labelContainer
|
||||
}
|
||||
|
||||
override fun getLabel(): View {
|
||||
return label
|
||||
}
|
||||
|
||||
override fun getSecondaryLabel(): View {
|
||||
return secondaryLabel
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.dagger.QSFragmentComponent;
|
||||
import com.android.systemui.qs.external.CustomTileStatePersister;
|
||||
import com.android.systemui.qs.external.TileServiceRequestController;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
@@ -95,6 +96,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
|
||||
private KeyguardBypassController mBypassController;
|
||||
@Mock
|
||||
private FalsingManager mFalsingManager;
|
||||
@Mock
|
||||
private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
|
||||
@Mock
|
||||
private TileServiceRequestController mTileServiceRequestController;
|
||||
|
||||
public QSFragmentTest() {
|
||||
super(QSFragment.class);
|
||||
@@ -108,6 +113,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
|
||||
when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
|
||||
when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);
|
||||
|
||||
when(mTileServiceRequestControllerBuilder.create(any()))
|
||||
.thenReturn(mTileServiceRequestController);
|
||||
|
||||
mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
|
||||
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
|
||||
new LayoutInflaterBuilder(mContext)
|
||||
@@ -136,7 +144,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
|
||||
() -> mock(AutoTileManager.class), mock(DumpManager.class),
|
||||
mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
|
||||
mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
|
||||
mock(SecureSettings.class), mock(CustomTileStatePersister.class));
|
||||
mock(SecureSettings.class), mock(CustomTileStatePersister.class),
|
||||
mTileServiceRequestControllerBuilder);
|
||||
qs.setHost(host);
|
||||
|
||||
qs.setListening(true);
|
||||
|
||||
@@ -21,6 +21,7 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -59,6 +60,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.external.CustomTile;
|
||||
import com.android.systemui.qs.external.CustomTileStatePersister;
|
||||
import com.android.systemui.qs.external.TileServiceKey;
|
||||
import com.android.systemui.qs.external.TileServiceRequestController;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
@@ -124,6 +126,10 @@ public class QSTileHostTest extends SysuiTestCase {
|
||||
private SecureSettings mSecureSettings;
|
||||
@Mock
|
||||
private CustomTileStatePersister mCustomTileStatePersister;
|
||||
@Mock
|
||||
private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
|
||||
@Mock
|
||||
private TileServiceRequestController mTileServiceRequestController;
|
||||
|
||||
private Handler mHandler;
|
||||
private TestableLooper mLooper;
|
||||
@@ -134,10 +140,14 @@ public class QSTileHostTest extends SysuiTestCase {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mLooper = TestableLooper.get(this);
|
||||
mHandler = new Handler(mLooper.getLooper());
|
||||
|
||||
when(mTileServiceRequestControllerBuilder.create(any()))
|
||||
.thenReturn(mTileServiceRequestController);
|
||||
|
||||
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
|
||||
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
|
||||
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
|
||||
mSecureSettings, mCustomTileStatePersister);
|
||||
mSecureSettings, mCustomTileStatePersister, mTileServiceRequestControllerBuilder);
|
||||
setUpTileFactory();
|
||||
|
||||
when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
|
||||
@@ -371,11 +381,12 @@ public class QSTileHostTest extends SysuiTestCase {
|
||||
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
|
||||
BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
|
||||
UiEventLogger uiEventLogger, UserTracker userTracker,
|
||||
SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) {
|
||||
SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
|
||||
TileServiceRequestController.Builder tileServiceRequestControllerBuilder) {
|
||||
super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
|
||||
tunerService, autoTiles, dumpManager, broadcastDispatcher,
|
||||
Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings,
|
||||
customTileStatePersister);
|
||||
customTileStatePersister, tileServiceRequestControllerBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
138
packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
vendored
Normal file
138
packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.qs.external
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.plugins.qs.QSTileView
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class TileRequestDialogTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private const val APP_NAME = "App name"
|
||||
private const val LABEL = "Label"
|
||||
}
|
||||
|
||||
private lateinit var dialog: TileRequestDialog
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
// Create in looper so we can make sure that the tile is fully updated
|
||||
TestableLooper.get(this).runWithLooper {
|
||||
dialog = TileRequestDialog(mContext)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
if (this::dialog.isInitialized) {
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun useCorrectTheme() {
|
||||
assertThat(dialog.context.themeResId).isEqualTo(R.style.TileRequestDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setTileData_hasCorrectViews() {
|
||||
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
|
||||
val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
|
||||
|
||||
dialog.setTileData(tileData)
|
||||
dialog.show()
|
||||
|
||||
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
|
||||
|
||||
assertThat(content.childCount).isEqualTo(2)
|
||||
assertThat(content.getChildAt(0)).isInstanceOf(TextView::class.java)
|
||||
assertThat(content.getChildAt(1)).isInstanceOf(QSTileView::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setTileData_hasCorrectAppName() {
|
||||
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
|
||||
val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
|
||||
|
||||
dialog.setTileData(tileData)
|
||||
dialog.show()
|
||||
|
||||
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
|
||||
val text = content.getChildAt(0) as TextView
|
||||
assertThat(text.text.toString()).contains(APP_NAME)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setTileData_hasCorrectLabel() {
|
||||
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
|
||||
val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
|
||||
|
||||
dialog.setTileData(tileData)
|
||||
dialog.show()
|
||||
|
||||
TestableLooper.get(this).processAllMessages()
|
||||
|
||||
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
|
||||
val tile = content.getChildAt(1) as QSTileView
|
||||
assertThat((tile.label as TextView).text.toString()).isEqualTo(LABEL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setTileData_hasIcon() {
|
||||
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
|
||||
val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
|
||||
|
||||
dialog.setTileData(tileData)
|
||||
dialog.show()
|
||||
|
||||
TestableLooper.get(this).processAllMessages()
|
||||
|
||||
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
|
||||
val tile = content.getChildAt(1) as QSTileView
|
||||
assertThat((tile.icon.iconView as ImageView).drawable).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setTileData_nullIcon_hasIcon() {
|
||||
val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, null)
|
||||
|
||||
dialog.setTileData(tileData)
|
||||
dialog.show()
|
||||
|
||||
TestableLooper.get(this).processAllMessages()
|
||||
|
||||
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
|
||||
val tile = content.getChildAt(1) as QSTileView
|
||||
assertThat((tile.icon.iconView as ImageView).drawable).isNotNull()
|
||||
}
|
||||
}
|
||||
162
packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
vendored
Normal file
162
packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.qs.external
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.Icon
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.qs.QSTileHost
|
||||
import com.android.systemui.statusbar.commandline.CommandRegistry
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.anyString
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.function.Consumer
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class TileServiceRequestControllerTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
|
||||
private const val TEST_APP_NAME = "App"
|
||||
private const val TEST_LABEL = "Label"
|
||||
}
|
||||
|
||||
@Mock
|
||||
private lateinit var tileRequestDialog: TileRequestDialog
|
||||
@Mock
|
||||
private lateinit var qsTileHost: QSTileHost
|
||||
@Mock
|
||||
private lateinit var commandRegistry: CommandRegistry
|
||||
@Mock
|
||||
private lateinit var icon: Icon
|
||||
|
||||
private lateinit var controller: TileServiceRequestController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
// Tile not present by default
|
||||
`when`(qsTileHost.indexOf(anyString())).thenReturn(-1)
|
||||
|
||||
controller = TileServiceRequestController(qsTileHost, commandRegistry) {
|
||||
tileRequestDialog
|
||||
}
|
||||
|
||||
controller.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requestTileAdd_dataIsPassedToDialog() {
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
|
||||
|
||||
verify(tileRequestDialog).setTileData(
|
||||
TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tileAlreadyAdded_correctResult() {
|
||||
`when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
|
||||
|
||||
val callback = Callback()
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
|
||||
|
||||
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
|
||||
verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showAllUsers_set() {
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
|
||||
verify(tileRequestDialog).setShowForAllUsers(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancelOnTouchOutside_set() {
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
|
||||
verify(tileRequestDialog).setCanceledOnTouchOutside(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancelListener_dismissResult() {
|
||||
val cancelListenerCaptor =
|
||||
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
|
||||
|
||||
val callback = Callback()
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
|
||||
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
|
||||
|
||||
cancelListenerCaptor.value.onCancel(tileRequestDialog)
|
||||
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
|
||||
verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun positiveActionListener_tileAddedResult() {
|
||||
val clickListenerCaptor =
|
||||
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
|
||||
|
||||
val callback = Callback()
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
|
||||
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
|
||||
|
||||
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
|
||||
|
||||
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
|
||||
verify(qsTileHost).addTile(TEST_COMPONENT, /* end */ true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun negativeActionListener_tileNotAddedResult() {
|
||||
val clickListenerCaptor =
|
||||
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
|
||||
|
||||
val callback = Callback()
|
||||
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
|
||||
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
|
||||
|
||||
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
|
||||
|
||||
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE)
|
||||
verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
|
||||
}
|
||||
|
||||
private class Callback : Consumer<Int> {
|
||||
var lastAccepted: Int? = null
|
||||
private set
|
||||
override fun accept(t: Int) {
|
||||
lastAccepted = t
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,12 +98,20 @@ public class TileServicesTest extends SysuiTestCase {
|
||||
private UserTracker mUserTracker;
|
||||
@Mock
|
||||
private SecureSettings mSecureSettings;
|
||||
@Mock
|
||||
private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
|
||||
@Mock
|
||||
private TileServiceRequestController mTileServiceRequestController;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mDependency.injectMockDependency(BluetoothController.class);
|
||||
mManagers = new ArrayList<>();
|
||||
|
||||
when(mTileServiceRequestControllerBuilder.create(any()))
|
||||
.thenReturn(mTileServiceRequestController);
|
||||
|
||||
QSTileHost host = new QSTileHost(mContext,
|
||||
mStatusBarIconController,
|
||||
mQSFactory,
|
||||
@@ -119,7 +127,8 @@ public class TileServicesTest extends SysuiTestCase {
|
||||
mUiEventLogger,
|
||||
mUserTracker,
|
||||
mSecureSettings,
|
||||
mock(CustomTileStatePersister.class));
|
||||
mock(CustomTileStatePersister.class),
|
||||
mTileServiceRequestControllerBuilder);
|
||||
mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
|
||||
mUserTracker);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user