summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fabian Kozynski <kozynski@google.com> 2021-08-11 11:52:20 -0400
committer Fabian Kozynski <kozynski@google.com> 2021-08-18 11:18:52 -0400
commit3c40fff6e68be3efe619543417b8e9b369c20fd1 (patch)
tree5f874281e6a1d9e7c2955869b5c7f5b04bc79d36
parent62962cb74a393af2a6607ae44ed14f7cf76cfba5 (diff)
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
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java4
-rw-r--r--packages/SystemUI/res/layout/tile_service_request_dialog.xml36
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt138
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt162
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java11
14 files changed, 660 insertions, 6 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index 2213d1c5c844..9a9683dbfb5c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
@@ -60,6 +60,10 @@ public abstract class QSTileView extends LinearLayout {
public abstract int getDetailY();
+ public View getLabel() {
+ return null;
+ }
+
public View getLabelContainer() {
return null;
}
diff --git a/packages/SystemUI/res/layout/tile_service_request_dialog.xml b/packages/SystemUI/res/layout/tile_service_request_dialog.xml
new file mode 100644
index 000000000000..b431d444e15a
--- /dev/null
+++ b/packages/SystemUI/res/layout/tile_service_request_dialog.xml
@@ -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>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7d9da432ae35..45014f8a3de7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -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>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b9002c3db399..5e8494651e9d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d25474255bfc..7f4866f5a586 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -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" />
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 541ee2c4fe8f..94451302b256 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
new file mode 100644
index 000000000000..baf30186ea1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -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?
+ )
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
new file mode 100644
index 000000000000..e6da234fe9c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -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)
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 222539d49526..ee5e4dffab14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -286,6 +286,10 @@ open class QSTileViewImpl @JvmOverloads constructor(
return labelContainer
}
+ override fun getLabel(): View {
+ return label
+ }
+
override fun getSecondaryLabel(): View {
return secondaryLabel
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 109721f5b096..1ed34d9d534e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 9e97f801be3e..bf9d4b16bfe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
new file mode 100644
index 000000000000..d49673d0f572
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -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()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
new file mode 100644
index 000000000000..ce8e58ca9e61
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -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
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 2b1840462291..e027ab72f2ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -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);
}