Add API for requesting add controls from Provider

This API allows ControlsProviderService to request SystemUI to show UI
to the user to add a favorite. This can be called at any time (not just
when bound).

Test: atest
Fixes: 148936288
Change-Id: I8740b6a105ad64994e6df701f2604a4234a49154
This commit is contained in:
Fabian Kozynski
2020-02-05 13:01:04 -05:00
parent 8887c6da17
commit d6f484027d
4 changed files with 82 additions and 2 deletions

View File

@@ -32,6 +32,7 @@ package android {
field public static final String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
field @Deprecated public static final String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
field public static final String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
field public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
field public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
field public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
@@ -43410,6 +43411,7 @@ package android.service.controls {
method @NonNull public abstract java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherFor(@NonNull java.util.List<java.lang.String>);
method @Nullable public java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherForAllAvailable();
method @Nullable public java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherForSuggested();
method public static void requestAddControl(@NonNull android.content.Context, @NonNull android.content.ComponentName, @NonNull android.service.controls.Control);
field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
field @NonNull public static final String TAG = "ControlsProviderService";
}

View File

@@ -15,11 +15,14 @@
*/
package android.service.controls;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@@ -51,6 +54,20 @@ public abstract class ControlsProviderService extends Service {
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_CONTROLS =
"android.service.controls.ControlsProviderService";
/**
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ADD_CONTROL =
"android.service.controls.action.ADD_CONTROL";
/**
* @hide
*/
public static final String EXTRA_CONTROL =
"android.service.controls.extra.CONTROL";
/**
* @hide
*/
@@ -325,6 +342,33 @@ public abstract class ControlsProviderService extends Service {
}
}
/**
* Request SystemUI to prompt the user to add a control to favorites.
*
* @param context A context
* @param componentName Component name of the {@link ControlsProviderService}
* @param control A stateless control to show to the user
*/
public static void requestAddControl(@NonNull Context context,
@NonNull ComponentName componentName,
@NonNull Control control) {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(componentName);
Preconditions.checkNotNull(control);
final ComponentName sysuiComponent = ComponentName.unflattenFromString(
context.getResources().getString(
com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(ACTION_ADD_CONTROL);
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName);
intent.setPackage(sysuiComponent.getPackageName());
if (isStatelessControl(control)) {
intent.putExtra(EXTRA_CONTROL, control);
} else {
intent.putExtra(EXTRA_CONTROL, new Control.StatelessBuilder(control).build());
}
context.sendBroadcast(intent, Manifest.permission.BIND_CONTROLS);
}
private static class SubscriptionAdapter extends IControlsSubscription.Stub {
final Subscription mSubscription;

View File

@@ -3081,8 +3081,7 @@
<!-- Allows SystemUI to request third party controls.
<p>Should only be requested by the System and required by
ControlsService declarations.
@hide
{@link android.service.controls.ControlsProviderService} declarations.
-->
<permission android:name="android.permission.BIND_CONTROLS"
android:protectionLevel="signature" />

View File

@@ -25,9 +25,13 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -44,6 +48,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +64,11 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
public class ControlProviderServiceTest {
private static final ComponentName TEST_SYSUI_COMPONENT =
ComponentName.unflattenFromString("sysui/.test.cls");
private static final ComponentName TEST_COMPONENT =
ComponentName.unflattenFromString("test.pkg/.test.cls");
private IBinder mToken = new Binder();
@Mock
private IControlsActionCallback.Stub mActionCallback;
@@ -66,6 +76,12 @@ public class ControlProviderServiceTest {
private IControlsSubscriber.Stub mSubscriber;
@Mock
private IIntentSender mIIntentSender;
@Mock
private Resources mResources;
@Mock
private Context mContext;
@Captor
private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private PendingIntent mPendingIntent;
private FakeControlsProviderService mControlsProviderService;
@@ -81,6 +97,10 @@ public class ControlProviderServiceTest {
when(mSubscriber.asBinder()).thenCallRealMethod();
when(mSubscriber.queryLocalInterface(any())).thenReturn(mSubscriber);
when(mResources.getString(com.android.internal.R.string.config_systemUIServiceComponent))
.thenReturn(TEST_SYSUI_COMPONENT.flattenToString());
when(mContext.getResources()).thenReturn(mResources);
Bundle b = new Bundle();
b.putBinder(ControlsProviderService.CALLBACK_TOKEN, mToken);
Intent intent = new Intent();
@@ -223,6 +243,21 @@ public class ControlProviderServiceTest {
ControlAction.RESPONSE_OK);
}
@Test
public void testRequestAdd() {
Control control = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build();
ControlsProviderService.requestAddControl(mContext, TEST_COMPONENT, control);
verify(mContext).sendBroadcast(mIntentArgumentCaptor.capture(),
eq(Manifest.permission.BIND_CONTROLS));
Intent intent = mIntentArgumentCaptor.getValue();
assertEquals(ControlsProviderService.ACTION_ADD_CONTROL, intent.getAction());
assertEquals(TEST_SYSUI_COMPONENT.getPackageName(), intent.getPackage());
assertEquals(TEST_COMPONENT, intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME));
assertTrue(equals(control,
intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL)));
}
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;