diff --git a/core/api/current.txt b/core/api/current.txt index edd8fab7ed671..29538ab0729fc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6694,6 +6694,16 @@ package android.app { } public class StatusBarManager { + method public int requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + field public static final int TILE_ADD_REQUEST_ANSWER_FAILED_BAD_COMPONENT = 3; // 0x3 + field public static final int TILE_ADD_REQUEST_ANSWER_FAILED_MISMATCHED_PACKAGE = 1; // 0x1 + field public static final int TILE_ADD_REQUEST_ANSWER_FAILED_NOT_CURRENT_USER = 4; // 0x4 + field public static final int TILE_ADD_REQUEST_ANSWER_FAILED_REQUEST_IN_PROGRESS = 2; // 0x2 + field public static final int TILE_ADD_REQUEST_ANSWER_FAILED_UNKNOWN_REASON = 5; // 0x5 + field public static final int TILE_ADD_REQUEST_ANSWER_SUCCESS = 0; // 0x0 + field public static final int TILE_ADD_REQUEST_RESULT_TILE_ADDED = 2; // 0x2 + field public static final int TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED = 1; // 0x1 + field public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0; // 0x0 } public final class SyncNotedAppOp implements android.os.Parcelable { @@ -10696,6 +10706,7 @@ package android.content { field public static final String SEARCH_SERVICE = "search"; field public static final String SENSOR_SERVICE = "sensor"; field public static final String SHORTCUT_SERVICE = "shortcut"; + field public static final String STATUS_BAR_SERVICE = "statusbar"; field public static final String STORAGE_SERVICE = "storage"; field public static final String STORAGE_STATS_SERVICE = "storagestats"; field public static final String SYSTEM_HEALTH_SERVICE = "systemhealth"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 61b34f1fcc665..27d78e5b6fb1e 100755 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2353,7 +2353,6 @@ package android.content { field public static final String SECURE_ELEMENT_SERVICE = "secure_element"; field public static final String SMARTSPACE_SERVICE = "smartspace"; field public static final String STATS_MANAGER = "stats"; - field public static final String STATUS_BAR_SERVICE = "statusbar"; field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 77bcef3ae009f..d9a339025afd1 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -24,7 +24,9 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; +import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -35,11 +37,15 @@ import android.util.Pair; import android.util.Slog; import android.view.View; +import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Allows an app to control the status bar. @@ -206,6 +212,71 @@ public class StatusBarManager { /** @hide */ public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2; + /** + * Response indicating that the tile was not added. + */ + public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0; + /** + * Response indicating that the tile was already added and the user was not prompted. + */ + public static final int TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED = 1; + /** + * Response indicating that the tile was added. + */ + public static final int TILE_ADD_REQUEST_RESULT_TILE_ADDED = 2; + /** @hide */ + public static final int TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED = 3; + + /** @hide */ + @IntDef(prefix = {"TILE_ADD_REQUEST_RESULT_"}, value = { + TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED, + TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED, + TILE_ADD_REQUEST_RESULT_TILE_ADDED, + TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestResult {} + + /** + * Indicates that the request was sent successfully. Does not indicate that the user + * has accepted the request. + */ + public static final int TILE_ADD_REQUEST_ANSWER_SUCCESS = 0; + /** + * Indicates that this package does not match that of the + * {@link android.service.quicksettings.TileService}. + */ + public static final int TILE_ADD_REQUEST_ANSWER_FAILED_MISMATCHED_PACKAGE = 1; + /** + * Indicates that there's a request in progress for this package. + */ + public static final int TILE_ADD_REQUEST_ANSWER_FAILED_REQUEST_IN_PROGRESS = 2; + /** + * Indicates that the component does not match an enabled + * {@link android.service.quicksettings.TileService} for the current user. + */ + public static final int TILE_ADD_REQUEST_ANSWER_FAILED_BAD_COMPONENT = 3; + /** + * Indicates that the user is not the current user. + */ + public static final int TILE_ADD_REQUEST_ANSWER_FAILED_NOT_CURRENT_USER = 4; + /** + * The request could not be processed due to an unkonwn reason. + */ + public static final int TILE_ADD_REQUEST_ANSWER_FAILED_UNKNOWN_REASON = 5; + + /** @hide */ + @IntDef(prefix = {"TILE_ADD_REQUEST_ANSWER_"}, value = { + TILE_ADD_REQUEST_ANSWER_SUCCESS, + TILE_ADD_REQUEST_ANSWER_FAILED_MISMATCHED_PACKAGE, + TILE_ADD_REQUEST_ANSWER_FAILED_REQUEST_IN_PROGRESS, + TILE_ADD_REQUEST_ANSWER_FAILED_BAD_COMPONENT, + TILE_ADD_REQUEST_ANSWER_FAILED_NOT_CURRENT_USER, + TILE_ADD_REQUEST_ANSWER_FAILED_UNKNOWN_REASON + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestAnswer {} + @UnsupportedAppUsage private Context mContext; private IStatusBarService mService; @@ -529,6 +600,69 @@ public class StatusBarManager { } } + /** + * Request to the user to add a {@link android.service.quicksettings.TileService} + * to the set of current QS tiles. + *

+ * Calling this will prompt the user to decide whether they want to add the shown + * {@link android.service.quicksettings.TileService} to their current tiles. The user can + * deny the request and the system can stop processing requests for a given + * {@link ComponentName} after a number of requests. + *

+ * The request will show to the user information about the tile: + *

+ *

+ * The user for which this will be added is determined from the {@link Context} used to retrieve + * this service, and must match the current user. + * + * @param tileServiceComponentName {@link ComponentName} of the + * {@link android.service.quicksettings.TileService} for the request. + * @param tileLabel label of the tile to show to the user. + * @param icon icon to use in the tile shown to the user. + * @param resultExecutor an executor to run the callback on + * @param resultCallback callback to indicate the {@link RequestResult}. + * @return whether the request was successfully sent. + * + * @see android.service.quicksettings.TileService + */ + @RequestAnswer + public int requestAddTileService( + @NonNull ComponentName tileServiceComponentName, + @NonNull CharSequence tileLabel, + @NonNull Icon icon, + @NonNull Executor resultExecutor, + @NonNull Consumer resultCallback + ) { + Objects.requireNonNull(tileServiceComponentName); + Objects.requireNonNull(tileLabel); + Objects.requireNonNull(icon); + Objects.requireNonNull(resultExecutor); + Objects.requireNonNull(resultCallback); + if (!tileServiceComponentName.getPackageName().equals(mContext.getPackageName())) { + return TILE_ADD_REQUEST_ANSWER_FAILED_MISMATCHED_PACKAGE; + } + int userId = mContext.getUserId(); + RequestResultCallback callbackProxy = new RequestResultCallback(resultExecutor, + resultCallback); + IStatusBarService svc = getService(); + try { + return svc.requestAddTile( + tileServiceComponentName, + tileLabel, + icon, + userId, + callbackProxy + ); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + return TILE_ADD_REQUEST_ANSWER_FAILED_UNKNOWN_REASON; + } + /** @hide */ public static String windowStateToString(int state) { if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; @@ -773,4 +907,25 @@ public class StatusBarManager { return new Pair(disable1, disable2); } } + + /** + * @hide + */ + static final class RequestResultCallback extends IAddTileResultCallback.Stub { + + @NonNull + private final Executor mExecutor; + @NonNull + private final Consumer mCallback; + + RequestResultCallback(@NonNull Executor executor, @NonNull Consumer callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onTileRequest(int userResponse) { + mExecutor.execute(() -> mCallback.accept(userResponse)); + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 1df4b207e1c6c..cd9c81ac4294a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4295,14 +4295,12 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a {@link - * android.app.StatusBarManager} for interacting with the status bar. + * android.app.StatusBarManager} for interacting with the status bar and quick settings. * * @see #getSystemService(String) * @see android.app.StatusBarManager * - * @hide */ - @SystemApi @SuppressLint("ServiceName") public static final String STATUS_BAR_SERVICE = "statusbar"; diff --git a/core/java/com/android/internal/statusbar/IAddTileResultCallback.aidl b/core/java/com/android/internal/statusbar/IAddTileResultCallback.aidl new file mode 100644 index 0000000000000..7053eef2858ae --- /dev/null +++ b/core/java/com/android/internal/statusbar/IAddTileResultCallback.aidl @@ -0,0 +1,22 @@ +/** + * 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.internal.statusbar; + +/** {@hide} */ +oneway interface IAddTileResultCallback { + void onTileRequest(int userResponse); +} \ No newline at end of file diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index b427e8be142d2..0f245e6821bfd 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -18,6 +18,7 @@ package com.android.internal.statusbar; import android.app.ITransientNotificationCallback; import android.content.ComponentName; +import android.graphics.drawable.Icon; import android.graphics.Rect; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; @@ -27,6 +28,7 @@ import android.os.ParcelFileDescriptor; import android.service.notification.StatusBarNotification; import android.view.InsetsVisibilities; +import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; @@ -286,4 +288,6 @@ oneway interface IStatusBar * Triggers a GC in the system and status bar. */ void runGcForTest(); + + void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index b3499db94c883..92031b4e14ae2 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -18,6 +18,7 @@ package com.android.internal.statusbar; import android.app.Notification; import android.content.ComponentName; +import android.graphics.drawable.Icon; import android.graphics.Rect; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; @@ -27,6 +28,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.internal.statusbar.StatusBarIcon; @@ -157,4 +159,6 @@ interface IStatusBarService * display. */ void suppressAmbientDisplay(boolean suppress); + + int requestAddTile(in ComponentName componentName, in CharSequence label, in Icon icon, int userId, in IAddTileResultCallback callback); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt index e6da234fe9c18..fe1a619f29af2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt @@ -17,17 +17,20 @@ package com.android.systemui.qs.external import android.app.Dialog +import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.internal.statusbar.IAddTileResultCallback 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 com.android.systemui.statusbar.CommandQueue import java.io.PrintWriter import java.util.function.Consumer import javax.inject.Inject @@ -39,24 +42,41 @@ private const val TAG = "TileServiceRequestController" */ class TileServiceRequestController constructor( private val qsTileHost: QSTileHost, + private val commandQueue: CommandQueue, 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 ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED + internal const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED + internal const val TILE_ALREADY_ADDED = + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED internal const val DISMISSED = 3 } + private val commandQueueCallback = object : CommandQueue.Callbacks { + override fun requestAddTile( + componentName: ComponentName, + appName: CharSequence, + label: CharSequence, + icon: Icon, + callback: IAddTileResultCallback + ) { + requestTileAdd(componentName, appName, label, icon) { + callback.onTileRequest(it) + } + } + } + fun init() { commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() } + commandQueue.addCallback(commandQueueCallback) } fun destroy() { commandRegistry.unregisterCommand("tile-service-add") + commandQueue.removeCallback(commandQueueCallback) } private fun addTile(componentName: ComponentName) { @@ -133,10 +153,11 @@ class TileServiceRequestController constructor( @SysUISingleton class Builder @Inject constructor( + private val commandQueue: CommandQueue, private val commandRegistry: CommandRegistry ) { fun create(qsTileHost: QSTileHost): TileServiceRequestController { - return TileServiceRequestController(qsTileHost, commandRegistry) + return TileServiceRequestController(qsTileHost, commandQueue, commandRegistry) } } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6676901997bf0..f95dd346f7278 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -34,6 +34,7 @@ import android.app.StatusBarManager.WindowType; import android.app.StatusBarManager.WindowVisibleState; import android.content.ComponentName; import android.content.Context; +import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.IBiometricSysuiReceiver; @@ -58,6 +59,7 @@ import android.view.WindowInsetsController.Behavior; import androidx.annotation.NonNull; import com.android.internal.os.SomeArgs; +import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.GcUtils; @@ -149,6 +151,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT; private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT; private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT; + private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -403,6 +406,16 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#setNavigationBarLumaSamplingEnabled(int, boolean) */ default void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {} + + /** + * @see IStatusBar#requestAddTile + */ + default void requestAddTile( + @NonNull ComponentName componentName, + @NonNull CharSequence appName, + @NonNull CharSequence label, + @NonNull Icon icon, + @NonNull IAddTileResultCallback callback) {} } public CommandQueue(Context context) { @@ -1108,6 +1121,23 @@ public class CommandQueue extends IStatusBar.Stub implements GcUtils.runGcAndFinalizersSync(); } + @Override + public void requestAddTile( + @NonNull ComponentName componentName, + @NonNull CharSequence appName, + @NonNull CharSequence label, + @NonNull Icon icon, + @NonNull IAddTileResultCallback callback + ) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = componentName; + args.arg2 = appName; + args.arg3 = label; + args.arg4 = icon; + args.arg5 = callback; + mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget(); + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1481,6 +1511,19 @@ public class CommandQueue extends IStatusBar.Stub implements msg.arg2 != 0); } break; + case MSG_TILE_SERVICE_REQUEST_ADD: + args = (SomeArgs) msg.obj; + ComponentName componentName = (ComponentName) args.arg1; + CharSequence appName = (CharSequence) args.arg2; + CharSequence label = (CharSequence) args.arg3; + Icon icon = (Icon) args.arg4; + IAddTileResultCallback callback = (IAddTileResultCallback) args.arg5; + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).requestAddTile( + componentName, appName, label, icon, callback); + } + args.recycle(); + break; } } } 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 index ce8e58ca9e619..70e971cdbf324 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -21,8 +21,10 @@ import android.content.DialogInterface import android.graphics.drawable.Icon import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IAddTileResultCallback import com.android.systemui.SysuiTestCase import com.android.systemui.qs.QSTileHost +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture @@ -36,6 +38,7 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyString +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -58,6 +61,8 @@ class TileServiceRequestControllerTest : SysuiTestCase() { @Mock private lateinit var commandRegistry: CommandRegistry @Mock + private lateinit var commandQueue: CommandQueue + @Mock private lateinit var icon: Icon private lateinit var controller: TileServiceRequestController @@ -69,7 +74,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { // Tile not present by default `when`(qsTileHost.indexOf(anyString())).thenReturn(-1) - controller = TileServiceRequestController(qsTileHost, commandRegistry) { + controller = TileServiceRequestController(qsTileHost, commandQueue, commandRegistry) { tileRequestDialog } @@ -152,11 +157,44 @@ class TileServiceRequestControllerTest : SysuiTestCase() { verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean()) } - private class Callback : Consumer { + @Test + fun commandQueueCallback_registered() { + verify(commandQueue).addCallback(any()) + } + + @Test + fun commandQueueCallback_dataPassedToDialog() { + val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) + + captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) + + verify(tileRequestDialog).setTileData( + TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon) + ) + } + + @Test + fun commandQueueCallback_callbackCalled() { + `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) + val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) + val c = Callback() + + captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c) + + assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) + } + + private class Callback : IAddTileResultCallback.Stub(), Consumer { var lastAccepted: Int? = null private set override fun accept(t: Int) { lastAccepted = t } + + override fun onTileRequest(r: Int) { + accept(r) + } } } \ No newline at end of file diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index b58ca1f4ef804..7ac9110dd3cf2 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -21,7 +21,9 @@ import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; import static android.view.Display.DEFAULT_DISPLAY; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManagerInternal; import android.app.ActivityThread; import android.app.ITransientNotificationCallback; import android.app.Notification; @@ -31,7 +33,11 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.IBiometricSysuiReceiver; @@ -53,6 +59,7 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.service.notification.NotificationStats; +import android.service.quicksettings.TileService; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -68,6 +75,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.os.TransferPipe; +import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -123,7 +131,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final Object mLock = new Object(); private final DeathRecipient mDeathRecipient = new DeathRecipient(); + private final ActivityManagerInternal mActivityManagerInternal; private final ActivityTaskManagerInternal mActivityTaskManager; + private final PackageManagerInternal mPackageManagerInternal; private int mCurrentUserId; private boolean mTracingEnabled; @@ -227,6 +237,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); displayManager.registerDisplayListener(this, mHandler); mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } @Override @@ -1640,6 +1652,96 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + private void checkCallingUidPackage(String packageName, int callingUid, int userId) { + int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId); + if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(packageUid)) { + throw new SecurityException("Package " + packageName + + " does not belong to the calling uid " + callingUid); + } + } + + private ResolveInfo isComponentValidTileService(ComponentName componentName, int userId) { + Intent intent = new Intent(TileService.ACTION_QS_TILE); + intent.setComponent(componentName); + ResolveInfo r = mPackageManagerInternal.resolveService(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0, userId, + Process.myUid()); + int enabled = mPackageManagerInternal.getComponentEnabledSetting( + componentName, Process.myUid(), userId); + if (r != null + && r.serviceInfo != null + && resolveEnabledComponent(r.serviceInfo.enabled, enabled) + && Manifest.permission.BIND_QUICK_SETTINGS_TILE.equals(r.serviceInfo.permission)) { + return r; + } else { + return null; + } + } + + private boolean resolveEnabledComponent(boolean defaultValue, int pmResult) { + if (pmResult == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + return true; + } + if (pmResult == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + return defaultValue; + } + return false; + } + + @Override + public int requestAddTile( + @NonNull ComponentName componentName, + @NonNull CharSequence label, + @NonNull Icon icon, + int userId, + @NonNull IAddTileResultCallback callback + ) { + int callingUid = Binder.getCallingUid(); + String packageName = componentName.getPackageName(); + + // Check calling user can act on behalf of current user + mActivityManagerInternal.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, + false, ActivityManagerInternal.ALLOW_NON_FULL, "requestAddTile", packageName); + + // Check calling uid matches package + checkCallingUidPackage(packageName, callingUid, userId); + + int currentUser = mActivityManagerInternal.getCurrentUserId(); + + // Check current user + if (userId != currentUser) { + return StatusBarManager.TILE_ADD_REQUEST_ANSWER_FAILED_NOT_CURRENT_USER; + } + + // We've checked that the package, component name and uid all match. + ResolveInfo r = isComponentValidTileService(componentName, userId); + if (r == null) { + return StatusBarManager.TILE_ADD_REQUEST_ANSWER_FAILED_BAD_COMPONENT; + } + + IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() { + @Override + public void onTileRequest(int i) throws RemoteException { + if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) { + i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED; + } + callback.onTileRequest(i); + } + }; + + CharSequence appName = r.serviceInfo.applicationInfo + .loadLabel(mContext.getPackageManager()); + if (mBar != null) { + try { + mBar.requestAddTile(componentName, appName, label, icon, proxyCallback); + return StatusBarManager.TILE_ADD_REQUEST_ANSWER_SUCCESS; + } catch (RemoteException e) { + Slog.e(TAG, "requestAddTile", e); + } + } + return StatusBarManager.TILE_ADD_REQUEST_ANSWER_FAILED_UNKNOWN_REASON; + } + public String[] getStatusBarIcons() { return mContext.getResources().getStringArray(R.array.config_statusBarIcons); }