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 2213d1c5c8449..9a9683dbfb5ce 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 0000000000000..b431d444e15a0
--- /dev/null
+++ b/packages/SystemUI/res/layout/tile_service_request_dialog.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ce0eba3ab9abd..de5dbb8d63071 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1634,4 +1634,8 @@
- 0.80
70dp
+
+ 304dp
+ 192dp
+ 24dp
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 409372655163a..6df34c4dad7aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3024,4 +3024,12 @@
Failed to connect to network
See all
+
+
+ %1$s wants to add the following tile to Quick Settings
+
+ Add tile
+
+ Do not add tile
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d25474255bfc5..7f4866f5a586b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -727,6 +727,16 @@
- @android:style/Animation.Dialog
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 541ee2c4fe8ff..94451302b2568 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, 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, 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, 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, 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, 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 0000000000000..baf30186ea1e3
--- /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(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 0000000000000..e6da234fe9c18
--- /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
+ ) {
+ if (isTileAlreadyAdded(componentName)) {
+ callback.accept(TILE_ALREADY_ADDED)
+ return
+ }
+ val dialogResponse = object : Consumer {
+ 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
+ ): 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) {
+ 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 " +
+ "