Merge "Add ThumbnailTemplate"

This commit is contained in:
Fabian Kozynski
2020-10-26 14:19:48 +00:00
committed by Android (Google) Code Review
9 changed files with 297 additions and 19 deletions

View File

@@ -43850,6 +43850,7 @@ package android.service.controls.templates {
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -43889,6 +43890,14 @@ package android.service.controls.templates {
field public static final int MODE_UNKNOWN = 0; // 0x0
}
public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
method @NonNull public CharSequence getContentDescription();
method public int getTemplateType();
method @NonNull public android.graphics.drawable.Icon getThumbnail();
method public boolean isActive();
}
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);

View File

@@ -206,8 +206,8 @@ public abstract class ControlsProviderService extends Service {
case MSG_SUBSCRIBE: {
final SubscribeMessage sMsg = (SubscribeMessage) msg.obj;
final SubscriberProxy proxy = new SubscriberProxy(false, mToken,
sMsg.mSubscriber);
final SubscriberProxy proxy = new SubscriberProxy(
ControlsProviderService.this, false, mToken, sMsg.mSubscriber);
ControlsProviderService.this.createPublisherFor(sMsg.mControlIds)
.subscribe(proxy);
@@ -251,6 +251,7 @@ public abstract class ControlsProviderService extends Service {
private IBinder mToken;
private IControlsSubscriber mCs;
private boolean mEnforceStateless;
private Context mContext;
SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) {
mEnforceStateless = enforceStateless;
@@ -258,6 +259,12 @@ public abstract class ControlsProviderService extends Service {
mCs = cs;
}
SubscriberProxy(Context context, boolean enforceStateless, IBinder token,
IControlsSubscriber cs) {
this(enforceStateless, token, cs);
mContext = context;
}
public void onSubscribe(Subscription subscription) {
try {
mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription));
@@ -273,6 +280,9 @@ public abstract class ControlsProviderService extends Service {
+ "Control.StatelessBuilder() to build the control.");
control = new Control.StatelessBuilder(control).build();
}
if (mContext != null) {
control.getControlTemplate().prepareTemplateForBinder(mContext);
}
mCs.onNext(mToken, control);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();

View File

@@ -20,6 +20,7 @@ import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.service.controls.Control;
import android.service.controls.actions.ControlAction;
@@ -78,6 +79,7 @@ public abstract class ControlTemplate {
TYPE_NO_TEMPLATE,
TYPE_TOGGLE,
TYPE_RANGE,
TYPE_THUMBNAIL,
TYPE_TOGGLE_RANGE,
TYPE_TEMPERATURE,
TYPE_STATELESS
@@ -104,6 +106,11 @@ public abstract class ControlTemplate {
*/
public static final @TemplateType int TYPE_RANGE = 2;
/**
* Type identifier of {@link ThumbnailTemplate}.
*/
public static final @TemplateType int TYPE_THUMBNAIL = 3;
/**
* Type identifier of {@link ToggleRangeTemplate}.
*/
@@ -168,6 +175,13 @@ public abstract class ControlTemplate {
mTemplateId = templateId;
}
/**
* Call to prepare values for Binder transport.
*
* @hide
*/
public void prepareTemplateForBinder(@NonNull Context context) {}
/**
*
* @param bundle
@@ -187,6 +201,8 @@ public abstract class ControlTemplate {
return new ToggleTemplate(bundle);
case TYPE_RANGE:
return new RangeTemplate(bundle);
case TYPE_THUMBNAIL:
return new ThumbnailTemplate(bundle);
case TYPE_TOGGLE_RANGE:
return new ToggleRangeTemplate(bundle);
case TYPE_TEMPERATURE:

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2020 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 android.service.controls.templates;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.controls.Control;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
/**
* A template for a {@link Control} that displays an image.
*/
public final class ThumbnailTemplate extends ControlTemplate {
private static final @TemplateType int TYPE = TYPE_THUMBNAIL;
private static final String KEY_ICON = "key_icon";
private static final String KEY_ACTIVE = "key_active";
private static final String KEY_CONTENT_DESCRIPTION = "key_content_description";
private final boolean mActive;
private final @NonNull Icon mThumbnail;
private final @NonNull CharSequence mContentDescription;
/**
* @param templateId the identifier for this template object
* @param active whether the image corresponds to an active (live) stream.
* @param thumbnail an image to display on the {@link Control}
* @param contentDescription a description of the image for accessibility.
*/
public ThumbnailTemplate(
@NonNull String templateId,
boolean active,
@NonNull Icon thumbnail,
@NonNull CharSequence contentDescription) {
super(templateId);
Preconditions.checkNotNull(thumbnail);
Preconditions.checkNotNull(contentDescription);
mActive = active;
mThumbnail = thumbnail;
mContentDescription = contentDescription;
}
/**
* @param b
* @hide
*/
ThumbnailTemplate(Bundle b) {
super(b);
mActive = b.getBoolean(KEY_ACTIVE);
mThumbnail = b.getParcelable(KEY_ICON);
mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, "");
}
/*
* @return {@code true} if the thumbnail corresponds to an active (live) stream.
*/
public boolean isActive() {
return mActive;
}
/**
* The {@link Icon} (image) displayed by this template.
*/
@NonNull
public Icon getThumbnail() {
return mThumbnail;
}
/**
* The description of the image returned by {@link ThumbnailTemplate#getThumbnail()}
*/
@NonNull
public CharSequence getContentDescription() {
return mContentDescription;
}
/**
* @return {@link ControlTemplate#TYPE_THUMBNAIL}
*/
@Override
public int getTemplateType() {
return TYPE;
}
/**
* Rescales the image down if necessary (in the case of a Bitmap).
*
* @hide
*/
@Override
public void prepareTemplateForBinder(@NonNull Context context) {
int width = context.getResources()
.getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_width);
int height = context.getResources()
.getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_height);
rescaleThumbnail(width, height);
}
private void rescaleThumbnail(int width, int height) {
mThumbnail.scaleDownIfNecessary(width, height);
}
/**
* @return
* @hide
*/
@Override
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putBoolean(KEY_ACTIVE, mActive);
b.putObject(KEY_ICON, mThumbnail);
b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
return b;
}
}

View File

@@ -857,4 +857,9 @@
<dimen name="waterfall_display_top_edge_size">0px</dimen>
<dimen name="waterfall_display_right_edge_size">0px</dimen>
<dimen name="waterfall_display_bottom_edge_size">0px</dimen>
<!-- The maximum height of a thumbnail in a ThumbnailTemplate. The image will be reduced to that height in case they are bigger. -->
<dimen name="controls_thumbnail_image_max_height">140dp</dimen>
<!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
<dimen name="controls_thumbnail_image_max_width">280dp</dimen>
</resources>

View File

@@ -4079,4 +4079,7 @@
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
</resources>

View File

@@ -32,6 +32,8 @@ import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -39,11 +41,14 @@ import android.os.RemoteException;
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
import android.service.controls.templates.ThumbnailTemplate;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -107,7 +112,8 @@ public class ControlProviderServiceTest {
mPendingIntent = new PendingIntent(mIIntentSender);
mControlsProviderService = new FakeControlsProviderService();
mControlsProviderService = new FakeControlsProviderService(
InstrumentationRegistry.getInstrumentation().getContext());
mControlsProvider = IControlsProvider.Stub.asInterface(
mControlsProviderService.onBind(intent));
}
@@ -134,7 +140,8 @@ public class ControlProviderServiceTest {
verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
subscriptionCaptor.getValue().request(1000);
verify(mSubscriber, times(2)).onNext(eq(mToken), controlCaptor.capture());
verify(mSubscriber, times(2))
.onNext(eq(mToken), controlCaptor.capture());
List<Control> values = controlCaptor.getAllValues();
assertTrue(equals(values.get(0), list.get(0)));
assertTrue(equals(values.get(1), list.get(1)));
@@ -210,26 +217,69 @@ public class ControlProviderServiceTest {
.setStatus(Control.STATUS_OK)
.build();
@SuppressWarnings("unchecked")
ArgumentCaptor<Control> controlCaptor =
ArgumentCaptor.forClass(Control.class);
ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
Control c = sendControlGetControl(control);
assertTrue(equals(c, control));
}
ArrayList<Control> list = new ArrayList<>();
list.add(control);
@Test
public void testThumbnailRescaled_bigger() throws RemoteException {
Context context = mControlsProviderService.getBaseContext();
int maxWidth = context.getResources().getDimensionPixelSize(
R.dimen.controls_thumbnail_image_max_width);
int maxHeight = context.getResources().getDimensionPixelSize(
R.dimen.controls_thumbnail_image_max_height);
mControlsProviderService.setControls(list);
int min = Math.min(maxWidth, maxHeight);
int max = Math.max(maxWidth, maxHeight);
mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
Bitmap bitmap = Bitmap.createBitmap(max * 2, max * 2, Bitmap.Config.ALPHA_8);
Icon icon = Icon.createWithBitmap(bitmap);
ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
subscriptionCaptor.getValue().request(1);
Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
.setTitle("TEST_TITLE")
.setStatus(Control.STATUS_OK)
.setControlTemplate(template)
.build();
verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
Control c = controlCaptor.getValue();
assertTrue(equals(c, list.get(0)));
Control c = sendControlGetControl(control);
ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
// Aspect ratio is kept
assertEquals(sentBitmap.getWidth(), sentBitmap.getHeight());
assertEquals(min, sentBitmap.getWidth());
}
@Test
public void testThumbnailRescaled_smaller() throws RemoteException {
Context context = mControlsProviderService.getBaseContext();
int maxWidth = context.getResources().getDimensionPixelSize(
R.dimen.controls_thumbnail_image_max_width);
int maxHeight = context.getResources().getDimensionPixelSize(
R.dimen.controls_thumbnail_image_max_height);
int min = Math.min(maxWidth, maxHeight);
Bitmap bitmap = Bitmap.createBitmap(min / 2, min / 2, Bitmap.Config.ALPHA_8);
Icon icon = Icon.createWithBitmap(bitmap);
ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
.setTitle("TEST_TITLE")
.setStatus(Control.STATUS_OK)
.setControlTemplate(template)
.build();
Control c = sendControlGetControl(control);
ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
assertEquals(bitmap.getHeight(), sentBitmap.getHeight());
assertEquals(bitmap.getWidth(), sentBitmap.getWidth());
}
@Test
@@ -257,6 +307,32 @@ public class ControlProviderServiceTest {
intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL)));
}
/**
* Sends the control through the publisher in {@code mControlsProviderService}, returning
* the control obtained by the subscriber
*/
private Control sendControlGetControl(Control control) throws RemoteException {
@SuppressWarnings("unchecked")
ArgumentCaptor<Control> controlCaptor =
ArgumentCaptor.forClass(Control.class);
ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
ArrayList<Control> list = new ArrayList<>();
list.add(control);
mControlsProviderService.setControls(list);
mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
subscriptionCaptor.getValue().request(1);
verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
return controlCaptor.getValue();
}
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;
@@ -276,6 +352,11 @@ public class ControlProviderServiceTest {
static class FakeControlsProviderService extends ControlsProviderService {
FakeControlsProviderService(Context context) {
super();
attachBaseContext(context);
}
private List<Control> mControls;
public void setControls(List<Control> controls) {

View File

@@ -102,6 +102,17 @@ public class ControlTemplateTest {
new RangeTemplate(TEST_ID, 0, 2, 1, -1, "%f");
}
@Test
public void testUnparcelingCorrectClass_thumbnail() {
ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, false, mIcon,
TEST_ACTION_DESCRIPTION);
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType());
assertTrue(fromParcel instanceof ThumbnailTemplate);
}
@Test
public void testUnparcelingCorrectClass_toggleRange() {
ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,

View File

@@ -41976,6 +41976,7 @@ package android.service.controls.templates {
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -42015,6 +42016,14 @@ package android.service.controls.templates {
field public static final int MODE_UNKNOWN = 0; // 0x0
}
public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
method @NonNull public CharSequence getContentDescription();
method public int getTemplateType();
method @NonNull public android.graphics.drawable.Icon getThumbnail();
method public boolean isActive();
}
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);