Merge "Add ThumbnailTemplate"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a26922c44e
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user