Merge "Removed @hide and Parcelable hierarchy"

This commit is contained in:
Matt Pietal
2020-01-24 11:38:44 +00:00
committed by Android (Google) Code Review
52 changed files with 1737 additions and 1016 deletions

View File

@@ -43122,6 +43122,307 @@ package android.service.chooser {
}
package android.service.controls {
public final class Control implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.app.PendingIntent getAppIntent();
method @NonNull public String getControlId();
method @NonNull public android.service.controls.templates.ControlTemplate getControlTemplate();
method @Nullable public android.content.res.ColorStateList getCustomColor();
method @Nullable public android.graphics.drawable.Icon getCustomIcon();
method public int getDeviceType();
method public int getStatus();
method @NonNull public CharSequence getStatusText();
method @Nullable public CharSequence getStructure();
method @NonNull public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
method @Nullable public CharSequence getZone();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.controls.Control> CREATOR;
field public static final int STATUS_DISABLED = 4; // 0x4
field public static final int STATUS_ERROR = 3; // 0x3
field public static final int STATUS_NOT_FOUND = 2; // 0x2
field public static final int STATUS_OK = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
}
public static final class Control.StatefulBuilder {
ctor public Control.StatefulBuilder(@NonNull String, @NonNull android.app.PendingIntent);
ctor public Control.StatefulBuilder(@NonNull android.service.controls.Control);
method @NonNull public android.service.controls.Control build();
method @NonNull public android.service.controls.Control.StatefulBuilder setAppIntent(@NonNull android.app.PendingIntent);
method @NonNull public android.service.controls.Control.StatefulBuilder setControlId(@NonNull String);
method @NonNull public android.service.controls.Control.StatefulBuilder setControlTemplate(@NonNull android.service.controls.templates.ControlTemplate);
method @NonNull public android.service.controls.Control.StatefulBuilder setCustomColor(@Nullable android.content.res.ColorStateList);
method @NonNull public android.service.controls.Control.StatefulBuilder setCustomIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.service.controls.Control.StatefulBuilder setDeviceType(int);
method @NonNull public android.service.controls.Control.StatefulBuilder setStatus(int);
method @NonNull public android.service.controls.Control.StatefulBuilder setStatusText(@NonNull CharSequence);
method @NonNull public android.service.controls.Control.StatefulBuilder setStructure(@Nullable CharSequence);
method @NonNull public android.service.controls.Control.StatefulBuilder setSubtitle(@NonNull CharSequence);
method @NonNull public android.service.controls.Control.StatefulBuilder setTitle(@NonNull CharSequence);
method @NonNull public android.service.controls.Control.StatefulBuilder setZone(@Nullable CharSequence);
}
public static final class Control.StatelessBuilder {
ctor public Control.StatelessBuilder(@NonNull String, @NonNull android.app.PendingIntent);
ctor public Control.StatelessBuilder(@NonNull android.service.controls.Control);
method @NonNull public android.service.controls.Control build();
method @NonNull public android.service.controls.Control.StatelessBuilder setAppIntent(@NonNull android.app.PendingIntent);
method @NonNull public android.service.controls.Control.StatelessBuilder setControlId(@NonNull String);
method @NonNull public android.service.controls.Control.StatelessBuilder setCustomColor(@Nullable android.content.res.ColorStateList);
method @NonNull public android.service.controls.Control.StatelessBuilder setCustomIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.service.controls.Control.StatelessBuilder setDeviceType(int);
method @NonNull public android.service.controls.Control.StatelessBuilder setStructure(@Nullable CharSequence);
method @NonNull public android.service.controls.Control.StatelessBuilder setSubtitle(@NonNull CharSequence);
method @NonNull public android.service.controls.Control.StatelessBuilder setTitle(@NonNull CharSequence);
method @NonNull public android.service.controls.Control.StatelessBuilder setZone(@Nullable CharSequence);
}
public abstract class ControlsProviderService extends android.app.Service {
ctor public ControlsProviderService();
method public abstract void loadAvailableControls(@NonNull java.util.function.Consumer<java.util.List<android.service.controls.Control>>);
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void performControlAction(@NonNull String, @NonNull android.service.controls.actions.ControlAction, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public abstract java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherFor(@NonNull java.util.List<java.lang.String>);
field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
field @NonNull public static final String TAG = "ControlsProviderService";
}
public class DeviceTypes {
method public static boolean validDeviceType(int);
field public static final int TYPE_AC_HEATER = 1; // 0x1
field public static final int TYPE_AC_UNIT = 2; // 0x2
field public static final int TYPE_AIR_FRESHENER = 3; // 0x3
field public static final int TYPE_AIR_PURIFIER = 4; // 0x4
field public static final int TYPE_AWNING = 33; // 0x21
field public static final int TYPE_BLINDS = 34; // 0x22
field public static final int TYPE_CAMERA = 50; // 0x32
field public static final int TYPE_CLOSET = 35; // 0x23
field public static final int TYPE_COFFEE_MAKER = 5; // 0x5
field public static final int TYPE_CURTAIN = 36; // 0x24
field public static final int TYPE_DEHUMIDIFIER = 6; // 0x6
field public static final int TYPE_DISHWASHER = 24; // 0x18
field public static final int TYPE_DISPLAY = 7; // 0x7
field public static final int TYPE_DOOR = 37; // 0x25
field public static final int TYPE_DOORBELL = 51; // 0x33
field public static final int TYPE_DRAWER = 38; // 0x26
field public static final int TYPE_DRYER = 25; // 0x19
field public static final int TYPE_FAN = 8; // 0x8
field public static final int TYPE_GARAGE = 39; // 0x27
field public static final int TYPE_GATE = 40; // 0x28
field public static final int TYPE_GENERIC_ARM_DISARM = -5; // 0xfffffffb
field public static final int TYPE_GENERIC_LOCK_UNLOCK = -4; // 0xfffffffc
field public static final int TYPE_GENERIC_ON_OFF = -1; // 0xffffffff
field public static final int TYPE_GENERIC_OPEN_CLOSE = -3; // 0xfffffffd
field public static final int TYPE_GENERIC_START_STOP = -2; // 0xfffffffe
field public static final int TYPE_GENERIC_TEMP_SETTING = -6; // 0xfffffffa
field public static final int TYPE_GENERIC_VIEWSTREAM = -7; // 0xfffffff9
field public static final int TYPE_HEATER = 47; // 0x2f
field public static final int TYPE_HOOD = 10; // 0xa
field public static final int TYPE_HUMIDIFIER = 11; // 0xb
field public static final int TYPE_KETTLE = 12; // 0xc
field public static final int TYPE_LIGHT = 13; // 0xd
field public static final int TYPE_LOCK = 45; // 0x2d
field public static final int TYPE_MICROWAVE = 14; // 0xe
field public static final int TYPE_MOP = 26; // 0x1a
field public static final int TYPE_MOWER = 27; // 0x1b
field public static final int TYPE_MULTICOOKER = 28; // 0x1c
field public static final int TYPE_OUTLET = 15; // 0xf
field public static final int TYPE_PERGOLA = 41; // 0x29
field public static final int TYPE_RADIATOR = 16; // 0x10
field public static final int TYPE_REFRIGERATOR = 48; // 0x30
field public static final int TYPE_REMOTE_CONTROL = 17; // 0x11
field public static final int TYPE_SECURITY_SYSTEM = 46; // 0x2e
field public static final int TYPE_SET_TOP = 18; // 0x12
field public static final int TYPE_SHOWER = 29; // 0x1d
field public static final int TYPE_SHUTTER = 42; // 0x2a
field public static final int TYPE_SPRINKLER = 30; // 0x1e
field public static final int TYPE_STANDMIXER = 19; // 0x13
field public static final int TYPE_STYLER = 20; // 0x14
field public static final int TYPE_SWITCH = 21; // 0x15
field public static final int TYPE_THERMOSTAT = 49; // 0x31
field public static final int TYPE_TV = 22; // 0x16
field public static final int TYPE_UNKNOWN = 0; // 0x0
field public static final int TYPE_VACUUM = 32; // 0x20
field public static final int TYPE_VALVE = 44; // 0x2c
field public static final int TYPE_WASHER = 31; // 0x1f
field public static final int TYPE_WATER_HEATER = 23; // 0x17
field public static final int TYPE_WINDOW = 43; // 0x2b
}
}
package android.service.controls.actions {
public final class BooleanAction extends android.service.controls.actions.ControlAction {
ctor public BooleanAction(@NonNull String, boolean);
ctor public BooleanAction(@NonNull String, boolean, @Nullable String);
method public int getActionType();
method public boolean getNewState();
}
public final class CommandAction extends android.service.controls.actions.ControlAction {
ctor public CommandAction(@NonNull String, @Nullable String);
ctor public CommandAction(@NonNull String);
method public int getActionType();
}
public abstract class ControlAction {
method public abstract int getActionType();
method @Nullable public String getChallengeValue();
method @NonNull public String getTemplateId();
method public static final boolean isValidResponse(int);
field @NonNull public static final android.service.controls.actions.ControlAction ERROR_ACTION;
field public static final int RESPONSE_CHALLENGE_ACK = 3; // 0x3
field public static final int RESPONSE_CHALLENGE_PASSPHRASE = 5; // 0x5
field public static final int RESPONSE_CHALLENGE_PIN = 4; // 0x4
field public static final int RESPONSE_FAIL = 2; // 0x2
field public static final int RESPONSE_OK = 1; // 0x1
field public static final int RESPONSE_UNKNOWN = 0; // 0x0
field public static final int TYPE_BOOLEAN = 1; // 0x1
field public static final int TYPE_COMMAND = 5; // 0x5
field public static final int TYPE_ERROR = -1; // 0xffffffff
field public static final int TYPE_FLOAT = 2; // 0x2
field public static final int TYPE_MODE = 4; // 0x4
field public static final int TYPE_MULTI_FLOAT = 3; // 0x3
}
public final class FloatAction extends android.service.controls.actions.ControlAction {
ctor public FloatAction(@NonNull String, float);
ctor public FloatAction(@NonNull String, float, @Nullable String);
method public int getActionType();
method public float getNewValue();
}
public final class ModeAction extends android.service.controls.actions.ControlAction {
ctor public ModeAction(@NonNull String, int, @Nullable String);
ctor public ModeAction(@NonNull String, int);
method public int getActionType();
method public int getNewMode();
}
public final class MultiFloatAction extends android.service.controls.actions.ControlAction {
ctor public MultiFloatAction(@NonNull String, @NonNull float[], @Nullable String);
ctor public MultiFloatAction(@NonNull String, @NonNull float[]);
method public int getActionType();
method @NonNull public float[] getNewValues();
}
}
package android.service.controls.templates {
public final class ControlButton implements android.os.Parcelable {
ctor public ControlButton(boolean, @NonNull CharSequence);
method public int describeContents();
method @NonNull public CharSequence getActionDescription();
method public boolean isChecked();
method @NonNull public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.controls.templates.ControlButton> CREATOR;
}
public abstract class ControlTemplate {
method @NonNull public String getTemplateId();
method public abstract int getTemplateType();
field @NonNull public static final android.service.controls.templates.ControlTemplate ERROR_TEMPLATE;
field @NonNull public static final android.service.controls.templates.ControlTemplate NO_TEMPLATE;
field public static final int TYPE_DISCRETE_TOGGLE = 4; // 0x4
field public static final int TYPE_ERROR = -1; // 0xffffffff
field public static final int TYPE_NONE = 0; // 0x0
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
}
public final class CoordinatedRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public CoordinatedRangeTemplate(@NonNull String, float, @NonNull android.service.controls.templates.RangeTemplate, @NonNull android.service.controls.templates.RangeTemplate);
ctor public CoordinatedRangeTemplate(@NonNull String, float, float, float, float, float, float, float, float, @Nullable CharSequence);
method public float getCurrentValueHigh();
method public float getCurrentValueLow();
method @NonNull public CharSequence getFormatString();
method public float getMaxValueHigh();
method public float getMaxValueLow();
method public float getMinGap();
method public float getMinValueHigh();
method public float getMinValueLow();
method @NonNull public android.service.controls.templates.RangeTemplate getRangeHigh();
method @NonNull public android.service.controls.templates.RangeTemplate getRangeLow();
method public float getStepValue();
method public int getTemplateType();
}
public final class DiscreteToggleTemplate extends android.service.controls.templates.ControlTemplate {
ctor public DiscreteToggleTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.ControlButton);
method @NonNull public android.service.controls.templates.ControlButton getNegativeButton();
method @NonNull public android.service.controls.templates.ControlButton getPositiveButton();
method public int getTemplateType();
}
public final class RangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public RangeTemplate(@NonNull String, float, float, float, float, @Nullable CharSequence);
method public float getCurrentValue();
method @NonNull public CharSequence getFormatString();
method public float getMaxValue();
method public float getMinValue();
method public float getStepValue();
method public int getTemplateType();
}
public final class StatelessTemplate extends android.service.controls.templates.ControlTemplate {
ctor public StatelessTemplate(@NonNull String);
method public int getTemplateType();
}
public final class TemperatureControlTemplate extends android.service.controls.templates.ControlTemplate {
ctor public TemperatureControlTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlTemplate, int, int, int);
method public int getCurrentActiveMode();
method public int getCurrentMode();
method public int getModes();
method @NonNull public android.service.controls.templates.ControlTemplate getTemplate();
method public int getTemplateType();
field public static final int FLAG_MODE_COOL = 8; // 0x8
field public static final int FLAG_MODE_ECO = 32; // 0x20
field public static final int FLAG_MODE_HEAT = 4; // 0x4
field public static final int FLAG_MODE_HEAT_COOL = 16; // 0x10
field public static final int FLAG_MODE_OFF = 2; // 0x2
field public static final int MODE_COOL = 3; // 0x3
field public static final int MODE_ECO = 5; // 0x5
field public static final int MODE_HEAT = 2; // 0x2
field public static final int MODE_HEAT_COOL = 4; // 0x4
field public static final int MODE_OFF = 1; // 0x1
field public static final int MODE_UNKNOWN = 0; // 0x0
}
public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ThumbnailTemplate(@NonNull String, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
method @NonNull public CharSequence getContentDescription();
method public int getTemplateType();
method @NonNull public android.graphics.drawable.Icon getThumbnail();
}
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);
method @NonNull public CharSequence getActionDescription();
method @NonNull public android.service.controls.templates.RangeTemplate getRange();
method public int getTemplateType();
method public boolean isChecked();
}
public final class ToggleTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton);
method @NonNull public CharSequence getContentDescription();
method public int getTemplateType();
method public boolean isChecked();
}
}
package android.service.dreams {
public class DreamService extends android.app.Service implements android.view.Window.Callback {

View File

@@ -19,12 +19,16 @@ package android.service.controls;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.controls.actions.ControlAction;
import android.service.controls.templates.ControlTemplate;
import android.service.controls.templates.ControlTemplateWrapper;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -51,9 +55,8 @@ import java.lang.annotation.RetentionPolicy;
* <p>
* An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
* allows for further actions should be provided.
* @hide
*/
public class Control implements Parcelable {
public final class Control implements Parcelable {
private static final String TAG = "Control";
private static final int NUM_STATUS = 5;
@@ -99,6 +102,10 @@ public class Control implements Parcelable {
private final @Nullable CharSequence mStructure;
private final @Nullable CharSequence mZone;
private final @NonNull PendingIntent mAppIntent;
private final @Nullable Icon mCustomIcon;
private final @Nullable ColorStateList mCustomColor;
private final @Status int mStatus;
private final @NonNull ControlTemplate mControlTemplate;
private final @NonNull CharSequence mStatusText;
@@ -113,14 +120,21 @@ public class Control implements Parcelable {
* @param zone
* @param appIntent a {@link PendingIntent} linking to a page to interact with the
* corresponding device.
* @param customIcon
* @param customColor
* @param status
* @param controlTemplate
* @param statusText
*/
public Control(@NonNull String controlId,
Control(@NonNull String controlId,
@DeviceTypes.DeviceType int deviceType,
@NonNull CharSequence title,
@NonNull CharSequence subtitle,
@Nullable CharSequence structure,
@Nullable CharSequence zone,
@NonNull PendingIntent appIntent,
@Nullable Icon customIcon,
@Nullable ColorStateList customColor,
@Status int status,
@NonNull ControlTemplate controlTemplate,
@NonNull CharSequence statusText) {
@@ -142,6 +156,10 @@ public class Control implements Parcelable {
mStructure = structure;
mZone = zone;
mAppIntent = appIntent;
mCustomColor = customColor;
mCustomIcon = customIcon;
if (status < 0 || status >= NUM_STATUS) {
mStatus = STATUS_UNKNOWN;
Log.e(TAG, "Status unknown:" + status);
@@ -152,7 +170,11 @@ public class Control implements Parcelable {
mStatusText = statusText;
}
public Control(Parcel in) {
/**
* @param in
* @hide
*/
Control(Parcel in) {
mControlId = in.readString();
mDeviceType = in.readInt();
mTitle = in.readCharSequence();
@@ -168,8 +190,22 @@ public class Control implements Parcelable {
mZone = null;
}
mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
if (in.readByte() == (byte) 1) {
mCustomIcon = Icon.CREATOR.createFromParcel(in);
} else {
mCustomIcon = null;
}
if (in.readByte() == (byte) 1) {
mCustomColor = ColorStateList.CREATOR.createFromParcel(in);
} else {
mCustomColor = null;
}
mStatus = in.readInt();
mControlTemplate = ControlTemplate.CREATOR.createFromParcel(in);
ControlTemplateWrapper wrapper = ControlTemplateWrapper.CREATOR.createFromParcel(in);
mControlTemplate = wrapper.getWrappedTemplate();
mStatusText = in.readCharSequence();
}
@@ -208,6 +244,16 @@ public class Control implements Parcelable {
return mAppIntent;
}
@Nullable
public Icon getCustomIcon() {
return mCustomIcon;
}
@Nullable
public ColorStateList getCustomColor() {
return mCustomColor;
}
@Status
public int getStatus() {
return mStatus;
@@ -229,7 +275,7 @@ public class Control implements Parcelable {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mControlId);
dest.writeInt(mDeviceType);
dest.writeCharSequence(mTitle);
@@ -247,14 +293,27 @@ public class Control implements Parcelable {
dest.writeByte((byte) 0);
}
mAppIntent.writeToParcel(dest, flags);
if (mCustomIcon != null) {
dest.writeByte((byte) 1);
mCustomIcon.writeToParcel(dest, flags);
} else {
dest.writeByte((byte) 0);
}
if (mCustomColor != null) {
dest.writeByte((byte) 1);
mCustomColor.writeToParcel(dest, flags);
} else {
dest.writeByte((byte) 0);
}
dest.writeInt(mStatus);
mControlTemplate.writeToParcel(dest, flags);
new ControlTemplateWrapper(mControlTemplate).writeToParcel(dest, flags);
dest.writeCharSequence(mStatusText);
}
public static final Creator<Control> CREATOR = new Creator<Control>() {
public static final @NonNull Creator<Control> CREATOR = new Creator<Control>() {
@Override
public Control createFromParcel(Parcel source) {
public Control createFromParcel(@NonNull Parcel source) {
return new Control(source);
}
@@ -275,25 +334,25 @@ public class Control implements Parcelable {
* <li> Subtitle: {@code ""}
* </ul>
* This fixes the values relating to state of the {@link Control} as required by
* {@link ControlsProviderService#onLoad}:
* {@link ControlsProviderService#loadAvailableControls}:
* <ul>
* <li> Status: {@link Status#STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#NO_TEMPLATE}
* <li> Status text: {@code ""}
* </ul>
*/
public static class StatelessBuilder {
@SuppressLint("MutableBareField")
public static final class StatelessBuilder {
private static final String TAG = "StatelessBuilder";
protected @NonNull String mControlId;
protected @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
protected @NonNull CharSequence mTitle = "";
protected @NonNull CharSequence mSubtitle = "";
protected @Nullable CharSequence mStructure;
protected @Nullable CharSequence mZone;
protected @NonNull PendingIntent mAppIntent;
protected @Status int mStatus = STATUS_UNKNOWN;
protected @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
protected @NonNull CharSequence mStatusText = "";
private @NonNull String mControlId;
private @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
private @NonNull CharSequence mTitle = "";
private @NonNull CharSequence mSubtitle = "";
private @Nullable CharSequence mStructure;
private @Nullable CharSequence mZone;
private @NonNull PendingIntent mAppIntent;
private @Nullable Icon mCustomIcon;
private @Nullable ColorStateList mCustomColor;
/**
* @param controlId the identifier for the {@link Control}.
@@ -320,6 +379,8 @@ public class Control implements Parcelable {
mStructure = control.mStructure;
mZone = control.mZone;
mAppIntent = control.mAppIntent;
mCustomIcon = control.mCustomIcon;
mCustomColor = control.mCustomColor;
}
/**
@@ -385,6 +446,18 @@ public class Control implements Parcelable {
return this;
}
@NonNull
public StatelessBuilder setCustomIcon(@Nullable Icon customIcon) {
mCustomIcon = customIcon;
return this;
}
@NonNull
public StatelessBuilder setCustomColor(@Nullable ColorStateList customColor) {
mCustomColor = customColor;
return this;
}
/**
* Build a {@link Control}
* @return a valid {@link Control}
@@ -398,14 +471,42 @@ public class Control implements Parcelable {
mStructure,
mZone,
mAppIntent,
mStatus,
mControlTemplate,
mStatusText);
mCustomIcon,
mCustomColor,
STATUS_UNKNOWN,
ControlTemplate.NO_TEMPLATE,
"");
}
}
public static class StatefulBuilder extends StatelessBuilder {
/**
* Builder class for {@link Control}.
*
* This class facilitates the creation of {@link Control}.
* It provides the following defaults for non-optional parameters:
* <ul>
* <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
* <li> Title: {@code ""}
* <li> Subtitle: {@code ""}
* <li> Status: {@link Status#STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#NO_TEMPLATE}
* <li> Status text: {@code ""}
* </ul>
*/
public static final class StatefulBuilder {
private static final String TAG = "StatefulBuilder";
private @NonNull String mControlId;
private @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
private @NonNull CharSequence mTitle = "";
private @NonNull CharSequence mSubtitle = "";
private @Nullable CharSequence mStructure;
private @Nullable CharSequence mZone;
private @NonNull PendingIntent mAppIntent;
private @Nullable Icon mCustomIcon;
private @Nullable ColorStateList mCustomColor;
private @Status int mStatus = STATUS_UNKNOWN;
private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
private @NonNull CharSequence mStatusText = "";
/**
* @param controlId the identifier for the {@link Control}.
@@ -413,11 +514,27 @@ public class Control implements Parcelable {
*/
public StatefulBuilder(@NonNull String controlId,
@NonNull PendingIntent appIntent) {
super(controlId, appIntent);
Preconditions.checkNotNull(controlId);
Preconditions.checkNotNull(appIntent);
mControlId = controlId;
mAppIntent = appIntent;
}
/**
* Creates a {@link StatelessBuilder} using an existing {@link Control} as a base.
* @param control base for the builder.
*/
public StatefulBuilder(@NonNull Control control) {
super(control);
Preconditions.checkNotNull(control);
mControlId = control.mControlId;
mDeviceType = control.mDeviceType;
mTitle = control.mTitle;
mSubtitle = control.mSubtitle;
mStructure = control.mStructure;
mZone = control.mZone;
mAppIntent = control.mAppIntent;
mCustomIcon = control.mCustomIcon;
mCustomColor = control.mCustomColor;
mStatus = control.mStatus;
mControlTemplate = control.mControlTemplate;
mStatusText = control.mStatusText;
@@ -429,13 +546,19 @@ public class Control implements Parcelable {
*/
@NonNull
public StatefulBuilder setControlId(@NonNull String controlId) {
super.setControlId(controlId);
Preconditions.checkNotNull(controlId);
mControlId = controlId;
return this;
}
@NonNull
public StatefulBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
super.setDeviceType(deviceType);
if (!DeviceTypes.validDeviceType(deviceType)) {
Log.e(TAG, "Invalid device type:" + deviceType);
mDeviceType = DeviceTypes.TYPE_UNKNOWN;
} else {
mDeviceType = deviceType;
}
return this;
}
@@ -445,25 +568,27 @@ public class Control implements Parcelable {
*/
@NonNull
public StatefulBuilder setTitle(@NonNull CharSequence title) {
super.setTitle(title);
Preconditions.checkNotNull(title);
mTitle = title;
return this;
}
@NonNull
public StatefulBuilder setSubtitle(@NonNull CharSequence subtitle) {
super.setSubtitle(subtitle);
Preconditions.checkNotNull(subtitle);
mSubtitle = subtitle;
return this;
}
@NonNull
public StatefulBuilder setStructure(@Nullable CharSequence structure) {
super.setStructure(structure);
mStructure = structure;
return this;
}
@NonNull
public StatefulBuilder setZone(@Nullable CharSequence zone) {
super.setZone(zone);
mZone = zone;
return this;
}
@@ -473,7 +598,20 @@ public class Control implements Parcelable {
*/
@NonNull
public StatefulBuilder setAppIntent(@NonNull PendingIntent appIntent) {
super.setAppIntent(appIntent);
Preconditions.checkNotNull(appIntent);
mAppIntent = appIntent;
return this;
}
@NonNull
public StatefulBuilder setCustomIcon(@Nullable Icon customIcon) {
mCustomIcon = customIcon;
return this;
}
@NonNull
public StatefulBuilder setCustomColor(@Nullable ColorStateList customColor) {
mCustomColor = customColor;
return this;
}
@@ -501,5 +639,21 @@ public class Control implements Parcelable {
mStatusText = statusText;
return this;
}
@NonNull
public Control build() {
return new Control(mControlId,
mDeviceType,
mTitle,
mSubtitle,
mStructure,
mZone,
mAppIntent,
mCustomIcon,
mCustomColor,
mStatus,
mControlTemplate,
mStatusText);
}
}
}

View File

@@ -27,6 +27,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
import android.service.controls.templates.ControlTemplate;
import android.text.TextUtils;
import android.util.Log;
@@ -35,150 +36,90 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.function.Consumer;
/**
* Service implementation allowing applications to contribute controls to the
* System UI.
* @hide
*/
public abstract class ControlsProviderService extends Service {
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String CONTROLS_ACTION = "android.service.controls.ControlsProviderService";
public static final String SERVICE_CONTROLS =
"android.service.controls.ControlsProviderService";
/**
* @hide
*/
public static final String CALLBACK_BUNDLE = "CALLBACK_BUNDLE";
public static final String CALLBACK_BINDER = "CALLBACK_BINDER";
/**
* @hide
*/
public static final String CALLBACK_TOKEN = "CALLBACK_TOKEN";
public final String TAG = getClass().getSimpleName();
public static final @NonNull String TAG = "ControlsProviderService";
private IControlsProviderCallback mCallback;
private IBinder mToken;
private RequestHandler mHandler;
/**
* Signal to retrieve all Controls. When complete, call
* {@link IControlsProviderCallback#onLoad} to inform the caller.
* Retrieve all available controls, using the stateless builder
* {@link Control.StatelessBuilder} to build each Control, then use the
* provided consumer to callback to the call originator.
*/
public abstract void load();
public abstract void loadAvailableControls(@NonNull Consumer<List<Control>> consumer);
/**
* Informs the service that the caller is listening for updates to the given controlIds.
* {@link IControlsProviderCallback#onRefreshState} should be called any time
* there are Control updates to render.
* Return a valid Publisher for the given controlIds. This publisher will be asked
* to provide updates for the given list of controlIds as long as the Subscription
* is valid.
*/
public abstract void subscribe(@NonNull List<String> controlIds);
/**
* Informs the service that the caller is done listening for updates,
* and any calls to {@link IControlsProviderCallback#onRefreshState} will be ignored.
*/
public abstract void unsubscribe();
@NonNull
public abstract Publisher<Control> publisherFor(@NonNull List<String> controlIds);
/**
* The user has interacted with a Control. The action is dictated by the type of
* {@link ControlAction} that was sent.
* {@link ControlAction} that was sent. A response can be sent via
* {@link Consumer#accept}, with the Integer argument being one of the provided
* {@link ControlAction.ResponseResult}. The Integer should indicate whether the action
* was received successfully, or if additional prompts should be presented to
* the user. Any visual control updates should be sent via the Publisher.
*/
public abstract void onAction(@NonNull String controlId, @NonNull ControlAction action);
/**
* Sends a list of the controls available from this service.
*
* The items in the list must not have state information (as created by
* {@link Control.StatelessBuilder}).
* @param controls
*/
public final void onLoad(@NonNull List<Control> controls) {
Preconditions.checkNotNull(controls);
List<Control> list = new ArrayList<>();
for (Control control: controls) {
if (control == null) {
Log.e(TAG, "onLoad: null control.");
}
if (isStateless(control)) {
list.add(control);
} else {
Log.w(TAG, "onLoad: control is not stateless.");
list.add(new Control.StatelessBuilder(control).build());
}
}
try {
mCallback.onLoad(mToken, list);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
/**
* Sends a list of the controls requested by {@link ControlsProviderService#subscribe} with
* their state.
* @param statefulControls
*/
public final void onRefreshState(@NonNull List<Control> statefulControls) {
Preconditions.checkNotNull(statefulControls);
try {
mCallback.onRefreshState(mToken, statefulControls);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
/**
* Sends the response of a command in the specified {@link Control}.
* @param controlId
* @param response
*/
public final void onControlActionResponse(
@NonNull String controlId, @ControlAction.ResponseResult int response) {
Preconditions.checkNotNull(controlId);
if (!ControlAction.isValidResponse(response)) {
Log.e(TAG, "Not valid response result: " + response);
response = ControlAction.RESPONSE_UNKNOWN;
}
try {
mCallback.onControlActionResponse(mToken, controlId, response);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
private boolean isStateless(Control control) {
return (control.getStatus() == Control.STATUS_UNKNOWN
&& control.getControlTemplate().getTemplateType() == ControlTemplate.TYPE_NONE
&& TextUtils.isEmpty(control.getStatusText()));
}
public abstract void performControlAction(@NonNull String controlId,
@NonNull ControlAction action, @NonNull Consumer<Integer> consumer);
@Override
public IBinder onBind(Intent intent) {
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
mHandler = new RequestHandler(Looper.getMainLooper());
Bundle bundle = intent.getBundleExtra(CALLBACK_BUNDLE);
IBinder callbackBinder = bundle.getBinder(CALLBACK_BINDER);
mToken = bundle.getBinder(CALLBACK_TOKEN);
mCallback = IControlsProviderCallback.Stub.asInterface(callbackBinder);
return new IControlsProvider.Stub() {
public void load() {
mHandler.sendEmptyMessage(RequestHandler.MSG_LOAD);
public void load(IControlsLoadCallback cb) {
mHandler.obtainMessage(RequestHandler.MSG_LOAD, cb).sendToTarget();
}
public void subscribe(List<String> ids) {
mHandler.obtainMessage(RequestHandler.MSG_SUBSCRIBE, ids).sendToTarget();
public void subscribe(List<String> controlIds,
IControlsSubscriber subscriber) {
SubscribeMessage msg = new SubscribeMessage(controlIds, subscriber);
mHandler.obtainMessage(RequestHandler.MSG_SUBSCRIBE, msg).sendToTarget();
}
public void unsubscribe() {
mHandler.sendEmptyMessage(RequestHandler.MSG_UNSUBSCRIBE);
}
public void onAction(String id, ControlAction action) {
ActionMessage msg = new ActionMessage(id, action);
mHandler.obtainMessage(RequestHandler.MSG_ON_ACTION, msg).sendToTarget();
public void action(String controlId, ControlActionWrapper action,
IControlsActionCallback cb) {
ActionMessage msg = new ActionMessage(controlId, action.getWrappedAction(), cb);
mHandler.obtainMessage(RequestHandler.MSG_ACTION, msg).sendToTarget();
}
};
}
@Override
public boolean onUnbind(Intent intent) {
mCallback = null;
public boolean onUnbind(@NonNull Intent intent) {
mHandler = null;
return true;
}
@@ -186,8 +127,7 @@ public abstract class ControlsProviderService extends Service {
private class RequestHandler extends Handler {
private static final int MSG_LOAD = 1;
private static final int MSG_SUBSCRIBE = 2;
private static final int MSG_UNSUBSCRIBE = 3;
private static final int MSG_ON_ACTION = 4;
private static final int MSG_ACTION = 3;
RequestHandler(Looper looper) {
super(looper);
@@ -196,30 +136,136 @@ public abstract class ControlsProviderService extends Service {
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_LOAD:
ControlsProviderService.this.load();
final IControlsLoadCallback cb = (IControlsLoadCallback) msg.obj;
ControlsProviderService.this.loadAvailableControls(consumerFor(cb));
break;
case MSG_SUBSCRIBE:
List<String> ids = (List<String>) msg.obj;
ControlsProviderService.this.subscribe(ids);
final SubscribeMessage sMsg = (SubscribeMessage) msg.obj;
final IControlsSubscriber cs = sMsg.mSubscriber;
Subscriber<Control> s = new Subscriber<Control>() {
public void onSubscribe(Subscription subscription) {
try {
cs.onSubscribe(mToken, new SubscriptionAdapter(subscription));
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
public void onNext(@NonNull Control statefulControl) {
Preconditions.checkNotNull(statefulControl);
try {
cs.onNext(mToken, statefulControl);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
public void onError(Throwable t) {
try {
cs.onError(mToken, t.toString());
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
public void onComplete() {
try {
cs.onComplete(mToken);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
}
};
ControlsProviderService.this.publisherFor(sMsg.mControlIds).subscribe(s);
break;
case MSG_UNSUBSCRIBE:
ControlsProviderService.this.unsubscribe();
break;
case MSG_ON_ACTION:
ActionMessage aMsg = (ActionMessage) msg.obj;
ControlsProviderService.this.onAction(aMsg.mId, aMsg.mAction);
case MSG_ACTION:
final ActionMessage aMsg = (ActionMessage) msg.obj;
ControlsProviderService.this.performControlAction(aMsg.mControlId,
aMsg.mAction, consumerFor(aMsg.mControlId, aMsg.mCb));
break;
}
}
private Consumer<Integer> consumerFor(final String controlId,
final IControlsActionCallback cb) {
return (@NonNull Integer response) -> {
Preconditions.checkNotNull(response);
if (!ControlAction.isValidResponse(response)) {
Log.e(TAG, "Not valid response result: " + response);
response = ControlAction.RESPONSE_UNKNOWN;
}
try {
cb.accept(mToken, controlId, response);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
};
}
private Consumer<List<Control>> consumerFor(IControlsLoadCallback cb) {
return (@NonNull List<Control> controls) -> {
Preconditions.checkNotNull(controls);
List<Control> list = new ArrayList<>();
for (Control control: controls) {
if (control == null) {
Log.e(TAG, "onLoad: null control.");
}
if (isStatelessControl(control)) {
list.add(control);
} else {
Log.w(TAG, "onLoad: control is not stateless.");
list.add(new Control.StatelessBuilder(control).build());
}
}
try {
cb.accept(mToken, list);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
}
};
}
private boolean isStatelessControl(Control control) {
return (control.getStatus() == Control.STATUS_UNKNOWN
&& control.getControlTemplate().getTemplateType() == ControlTemplate.TYPE_NONE
&& TextUtils.isEmpty(control.getStatusText()));
}
}
private class ActionMessage {
final String mId;
final ControlAction mAction;
private static class SubscriptionAdapter extends IControlsSubscription.Stub {
final Subscription mSubscription;
ActionMessage(String id, ControlAction action) {
this.mId = id;
SubscriptionAdapter(Subscription s) {
this.mSubscription = s;
}
public void request(long n) {
mSubscription.request(n);
}
public void cancel() {
mSubscription.cancel();
}
}
private static class ActionMessage {
final String mControlId;
final ControlAction mAction;
final IControlsActionCallback mCb;
ActionMessage(String controlId, ControlAction action, IControlsActionCallback cb) {
this.mControlId = controlId;
this.mAction = action;
this.mCb = cb;
}
}
private static class SubscribeMessage {
final List<String> mControlIds;
final IControlsSubscriber mSubscriber;
SubscribeMessage(List<String> controlIds, IControlsSubscriber subscriber) {
this.mControlIds = controlIds;
this.mSubscriber = subscriber;
}
}
}

View File

@@ -21,9 +21,6 @@ import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @hide
*/
public class DeviceTypes {
// Update this when adding new concrete types. Does not count TYPE_UNKNOWN

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, The Android Open Source Project
* 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.
@@ -14,6 +14,11 @@
* limitations under the License.
*/
package android.service.controls.templates;
package android.service.controls;
parcelable RangeTemplate;
/**
* @hide
*/
oneway interface IControlsActionCallback {
void accept(in IBinder token, in String controlId, int response);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, The Android Open Source Project
* 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.
@@ -14,6 +14,13 @@
* limitations under the License.
*/
package android.service.controls.templates;
package android.service.controls;
parcelable ToggleTemplate;
import android.service.controls.Control;
/**
* @hide
*/
oneway interface IControlsLoadCallback {
void accept(in IBinder token, in List<Control> controls);
}

View File

@@ -16,15 +16,20 @@
package android.service.controls;
import android.service.controls.actions.ControlAction;
import android.service.controls.IControlsActionCallback;
import android.service.controls.IControlsLoadCallback;
import android.service.controls.IControlsSubscriber;
import android.service.controls.actions.ControlActionWrapper;
/** @hide */
/**
* @hide
*/
oneway interface IControlsProvider {
void load();
void load(IControlsLoadCallback cb);
void subscribe(in List<String> controlIds);
void subscribe(in List<String> controlIds,
IControlsSubscriber subscriber);
void unsubscribe();
void onAction(in String controlId, in ControlAction action);
void action(in String controlId, in ControlActionWrapper action,
IControlsActionCallback cb);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, The Android Open Source Project
* 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.
@@ -17,12 +17,14 @@
package android.service.controls;
import android.service.controls.Control;
import android.service.controls.IControlsSubscription;
/** @hide */
oneway interface IControlsProviderCallback {
void onLoad(in IBinder token, in List<Control> controls);
void onRefreshState(in IBinder token, in List<Control> statefulControls);
void onControlActionResponse(in IBinder token, in String controlId, int response);
/**
* @hide
*/
oneway interface IControlsSubscriber {
void onSubscribe(in IBinder token, in IControlsSubscription cs);
void onNext(in IBinder token, in Control c);
void onError(in IBinder token, in String s);
void onComplete(in IBinder token);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, The Android Open Source Project
* 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.
@@ -14,6 +14,12 @@
* limitations under the License.
*/
package android.service.controls.templates;
package android.service.controls;
parcelable ControlTemplate;
/**
* @hide
*/
oneway interface IControlsSubscription {
void request(long n);
void cancel();
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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.actions;
parcelable BooleanAction;

View File

@@ -19,12 +19,10 @@ package android.service.controls.actions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.service.controls.templates.ToggleTemplate;
/**
* Action sent by a {@link ToggleTemplate}
* @hide
*/
public final class BooleanAction extends ControlAction {
@@ -54,6 +52,10 @@ public final class BooleanAction extends ControlAction {
mNewState = newState;
}
/**
* @param b
* @hide
*/
BooleanAction(Bundle b) {
super(b);
mNewState = b.getBoolean(KEY_NEW_STATE);
@@ -77,24 +79,15 @@ public final class BooleanAction extends ControlAction {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putBoolean(KEY_NEW_STATE, mNewState);
return b;
}
public static final @NonNull Creator<BooleanAction> CREATOR = new Creator<BooleanAction>() {
@Override
public BooleanAction createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new BooleanAction(source.readBundle());
}
@Override
public BooleanAction[] newArray(int size) {
return new BooleanAction[size];
}
};
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright (C) 2019 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.actions;
parcelable CommandAction;

View File

@@ -19,11 +19,7 @@ package android.service.controls.actions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
/**
* @hide
*/
public final class CommandAction extends ControlAction {
private static final @ActionType int TYPE = TYPE_COMMAND;
@@ -36,7 +32,11 @@ public final class CommandAction extends ControlAction {
this(templateId, null);
}
public CommandAction(Bundle b) {
/**
* @param b
* @hide
*/
CommandAction(Bundle b) {
super(b);
}
@@ -44,18 +44,4 @@ public final class CommandAction extends ControlAction {
public int getActionType() {
return TYPE;
}
public static final Creator<CommandAction> CREATOR = new Creator<CommandAction>() {
@Override
public CommandAction createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new CommandAction(source.readBundle());
}
@Override
public CommandAction[] newArray(int size) {
return new CommandAction[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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.actions;
parcelable ControlAction;

View File

@@ -21,10 +21,9 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.controls.IControlsProviderCallback;
import android.service.controls.IControlsActionCallback;
import android.service.controls.templates.ControlTemplate;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -36,20 +35,21 @@ import java.lang.annotation.RetentionPolicy;
*
* The action may have a value to authenticate the input, when the provider has requested it to
* complete the action.
* @hide
*/
public abstract class ControlAction implements Parcelable {
public abstract class ControlAction {
private static final String TAG = "ControlAction";
private static final String KEY_ACTION_TYPE = "key_action_type";
private static final String KEY_TEMPLATE_ID = "key_template_id";
private static final String KEY_CHALLENGE_VALUE = "key_challenge_value";
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TYPE_UNKNOWN,
TYPE_ERROR,
TYPE_BOOLEAN,
TYPE_FLOAT,
TYPE_MULTI_FLOAT,
@@ -57,15 +57,16 @@ public abstract class ControlAction implements Parcelable {
TYPE_COMMAND
})
public @interface ActionType {};
public static final ControlAction UNKNOWN_ACTION = new ControlAction() {
public static final @NonNull ControlAction ERROR_ACTION = new ControlAction() {
@Override
public int getActionType() {
return TYPE_UNKNOWN;
return TYPE_ERROR;
}
};
public static final @ActionType int TYPE_UNKNOWN = 0;
public static final @ActionType int TYPE_ERROR = -1;
/**
* The identifier of {@link BooleanAction}.
*/
@@ -104,27 +105,27 @@ public abstract class ControlAction implements Parcelable {
public static final @ResponseResult int RESPONSE_UNKNOWN = 0;
/**
* Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that
* Response code for {@link IControlsActionCallback#accept} indicating that
* the action has been performed. The action may still fail later and the state may not change.
*/
public static final @ResponseResult int RESPONSE_OK = 1;
/**
* Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that
* Response code for {@link IControlsActionCallback#accept} indicating that
* the action has failed.
*/
public static final @ResponseResult int RESPONSE_FAIL = 2;
/**
* Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that
* Response code for {@link IControlsActionCallback#accept} indicating that
* in order for the action to be performed, acknowledgment from the user is required.
*/
public static final @ResponseResult int RESPONSE_CHALLENGE_ACK = 3;
/**
* Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that
* Response code for {@link IControlsActionCallback#accept} indicating that
* in order for the action to be performed, a PIN is required.
*/
public static final @ResponseResult int RESPONSE_CHALLENGE_PIN = 4;
/**
* Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that
* Response code for {@link IControlsActionCallback#accept} indicating that
* in order for the action to be performed, an alphanumeric passphrase is required.
*/
public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5;
@@ -175,68 +176,55 @@ public abstract class ControlAction implements Parcelable {
return mChallengeValue;
}
@Override
public int describeContents() {
return 0;
}
@Override
public final void writeToParcel(Parcel dest, int flags) {
dest.writeInt(getActionType());
dest.writeBundle(getDataBundle());
}
/**
* Obtain a {@link Bundle} describing this object populated with data.
*
* Implementations in subclasses should populate the {@link Bundle} returned by
* {@link ControlAction}.
* @return a {@link Bundle} containing the data that represents this object.
* @hide
*/
@CallSuper
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = new Bundle();
b.putInt(KEY_ACTION_TYPE, getActionType());
b.putString(KEY_TEMPLATE_ID, mTemplateId);
b.putString(KEY_CHALLENGE_VALUE, mChallengeValue);
return b;
}
public static final @NonNull Creator<ControlAction> CREATOR = new Creator<ControlAction>() {
@Override
public ControlAction createFromParcel(Parcel source) {
int type = source.readInt();
return createActionFromType(type, source);
/**
* @param bundle
* @return
* @hide
*/
@NonNull
static ControlAction createActionFromBundle(@NonNull Bundle bundle) {
if (bundle == null) {
Log.e(TAG, "Null bundle");
return ERROR_ACTION;
}
@Override
public ControlAction[] newArray(int size) {
return new ControlAction[size];
}
};
private static ControlAction createActionFromType(@ActionType int type, Parcel source) {
switch(type) {
case TYPE_BOOLEAN:
return new BooleanAction(source.readBundle());
case TYPE_FLOAT:
return new FloatAction(source.readBundle());
case TYPE_MULTI_FLOAT:
return new MultiFloatAction(source.readBundle());
case TYPE_MODE:
return new ModeAction(source.readBundle());
case TYPE_COMMAND:
return new CommandAction(source.readBundle());
default:
source.readBundle();
return UNKNOWN_ACTION;
int type = bundle.getInt(KEY_ACTION_TYPE, TYPE_ERROR);
try {
switch (type) {
case TYPE_BOOLEAN:
return new BooleanAction(bundle);
case TYPE_FLOAT:
return new FloatAction(bundle);
case TYPE_MULTI_FLOAT:
return new MultiFloatAction(bundle);
case TYPE_MODE:
return new ModeAction(bundle);
case TYPE_COMMAND:
return new CommandAction(bundle);
case TYPE_ERROR:
default:
return ERROR_ACTION;
}
} catch (Exception e) {
Log.e(TAG, "Error creating action", e);
return ERROR_ACTION;
}
}
protected static void verifyType(@ActionType int type, @ActionType int thisType) {
if (type != thisType) {
throw new IllegalStateException("The type " + type + "does not match " + thisType);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* 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.
@@ -16,4 +16,4 @@
package android.service.controls.actions;
parcelable FloatAction;
parcelable ControlActionWrapper;

View File

@@ -0,0 +1,67 @@
/*
* 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.actions;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Wrapper for parceling/unparceling {@link ControlAction}.
* @hide
*/
public final class ControlActionWrapper implements Parcelable {
private final @NonNull ControlAction mControlAction;
public ControlActionWrapper(@NonNull ControlAction controlAction) {
Preconditions.checkNotNull(controlAction);
mControlAction = controlAction;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBundle(mControlAction.getDataBundle());
}
@NonNull
public ControlAction getWrappedAction() {
return mControlAction;
}
@Override
public int describeContents() {
return 0;
}
public static final @NonNull Creator<ControlActionWrapper> CREATOR =
new Creator<ControlActionWrapper>() {
@Override
public ControlActionWrapper createFromParcel(@NonNull Parcel in) {
return new ControlActionWrapper(
ControlAction.createActionFromBundle(in.readBundle()));
}
@Override
public ControlActionWrapper[] newArray(int size) {
return new ControlActionWrapper[size];
}
};
}

View File

@@ -19,13 +19,11 @@ package android.service.controls.actions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.service.controls.templates.RangeTemplate;
import android.service.controls.templates.ToggleRangeTemplate;
/**
* Action sent by a {@link RangeTemplate}, {@link ToggleRangeTemplate}.
* @hide
*/
public final class FloatAction extends ControlAction {
@@ -56,7 +54,11 @@ public final class FloatAction extends ControlAction {
mNewValue = newValue;
}
public FloatAction(Bundle b) {
/**
* @param b
* @hide
*/
FloatAction(Bundle b) {
super(b);
mNewValue = b.getFloat(KEY_NEW_VALUE);
}
@@ -76,24 +78,15 @@ public final class FloatAction extends ControlAction {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putFloat(KEY_NEW_VALUE, mNewValue);
return b;
}
public static final @NonNull Creator<FloatAction> CREATOR = new Creator<FloatAction>() {
@Override
public FloatAction createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new FloatAction(source.readBundle());
}
@Override
public FloatAction[] newArray(int size) {
return new FloatAction[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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.actions;
parcelable ModeAction;

View File

@@ -19,11 +19,7 @@ package android.service.controls.actions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
/**
* @hide
*/
public final class ModeAction extends ControlAction {
private static final @ActionType int TYPE = TYPE_MODE;
@@ -45,13 +41,22 @@ public final class ModeAction extends ControlAction {
this(templateId, newMode, null);
}
/**
* @param b
* @hide
*/
ModeAction(Bundle b) {
super(b);
mNewMode = b.getInt(KEY_MODE);
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putInt(KEY_MODE, mNewMode);
return b;
@@ -60,18 +65,4 @@ public final class ModeAction extends ControlAction {
public int getNewMode() {
return mNewMode;
}
public static final Creator<ModeAction> CREATOR = new Creator<ModeAction>() {
@Override
public ModeAction createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new ModeAction(source.readBundle());
}
@Override
public ModeAction[] newArray(int size) {
return new ModeAction[size];
}
};
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright (C) 2019 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.actions;
parcelable MultiFloatAction;

View File

@@ -19,14 +19,10 @@ package android.service.controls.actions;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
import com.android.internal.util.Preconditions;
/**
* @hide
*/
public final class MultiFloatAction extends ControlAction {
private static final String TAG = "MultiFloatAction";
@@ -58,6 +54,10 @@ public final class MultiFloatAction extends ControlAction {
this(templateId, newValues, null);
}
/**
* @param b
* @hide
*/
MultiFloatAction(Bundle b) {
super(b);
mNewValues = b.getFloatArray(KEY_VALUES);
@@ -68,24 +68,15 @@ public final class MultiFloatAction extends ControlAction {
return mNewValues.clone();
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putFloatArray(KEY_VALUES, mNewValues);
return b;
}
public static final Creator<MultiFloatAction> CREATOR = new Creator<MultiFloatAction>() {
@Override
public MultiFloatAction createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new MultiFloatAction(source.readBundle());
}
@Override
public MultiFloatAction[] newArray(int size) {
return new MultiFloatAction[size];
}
};
}

View File

@@ -24,7 +24,6 @@ import com.android.internal.util.Preconditions;
/**
* Button element for {@link ControlTemplate}.
* @hide
*/
public final class ControlButton implements Parcelable {
@@ -64,7 +63,8 @@ public final class ControlButton implements Parcelable {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
@NonNull
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeByte(mChecked ? (byte) 1 : (byte) 0);
dest.writeCharSequence(mActionDescription);
}
@@ -74,7 +74,7 @@ public final class ControlButton implements Parcelable {
mActionDescription = in.readCharSequence();
}
public static final Creator<ControlButton> CREATOR = new Creator<ControlButton>() {
public static final @NonNull Creator<ControlButton> CREATOR = new Creator<ControlButton>() {
@Override
public ControlButton createFromParcel(Parcel source) {
return new ControlButton(source);

View File

@@ -19,11 +19,11 @@ package android.service.controls.templates;
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.controls.Control;
import android.service.controls.actions.ControlAction;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -39,27 +39,37 @@ import java.lang.annotation.RetentionPolicy;
* associated state. The actions available to a given {@link Control} are determined by its
* {@link ControlTemplate}.
* @see ControlAction
* @hide
*/
public abstract class ControlTemplate implements Parcelable {
public abstract class ControlTemplate {
private static final String TAG = "ControlTemplate";
private static final String KEY_TEMPLATE_ID = "key_template_id";
private static final String KEY_TEMPLATE_TYPE = "key_template_type";
/**
* Singleton representing a {@link Control} with no input.
*/
public static final ControlTemplate NO_TEMPLATE = new ControlTemplate("") {
public static final @NonNull ControlTemplate NO_TEMPLATE = new ControlTemplate("") {
@Override
public int getTemplateType() {
return TYPE_NONE;
}
};
public static final @NonNull ControlTemplate ERROR_TEMPLATE = new ControlTemplate("") {
@Override
public int getTemplateType() {
return TYPE_ERROR;
}
};
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TYPE_ERROR,
TYPE_NONE,
TYPE_TOGGLE,
TYPE_RANGE,
@@ -72,47 +82,50 @@ public abstract class ControlTemplate implements Parcelable {
})
public @interface TemplateType {}
public static final @TemplateType int TYPE_ERROR = -1;
/**
* Type identifier of {@link ControlTemplate#NO_TEMPLATE}.
*/
public static final int TYPE_NONE = 0;
public static final @TemplateType int TYPE_NONE = 0;
/**
* Type identifier of {@link ToggleTemplate}.
*/
public static final int TYPE_TOGGLE = 1;
public static final @TemplateType int TYPE_TOGGLE = 1;
/**
* Type identifier of {@link RangeTemplate}.
*/
public static final int TYPE_RANGE = 2;
public static final @TemplateType int TYPE_RANGE = 2;
/**
* Type identifier of {@link ThumbnailTemplate}.
*/
public static final int TYPE_THUMBNAIL = 3;
public static final @TemplateType int TYPE_THUMBNAIL = 3;
/**
* Type identifier of {@link DiscreteToggleTemplate}.
*/
public static final int TYPE_DISCRETE_TOGGLE = 4;
public static final @TemplateType int TYPE_DISCRETE_TOGGLE = 4;
/**
* @hide
*/
public static final int TYPE_COORD_RANGE = 5;
public static final @TemplateType int TYPE_COORD_RANGE = 5;
public static final int TYPE_TOGGLE_RANGE = 6;
public static final @TemplateType int TYPE_TOGGLE_RANGE = 6;
public static final int TYPE_TEMPERATURE = 7;
public static final @TemplateType int TYPE_TEMPERATURE = 7;
public static final int TYPE_STATELESS = 8;
public static final @TemplateType int TYPE_STATELESS = 8;
private @NonNull final String mTemplateId;
/**
* @return the identifier for this object.
*/
@NonNull
public String getTemplateId() {
return mTemplateId;
}
@@ -122,24 +135,16 @@ public abstract class ControlTemplate implements Parcelable {
*/
public abstract @TemplateType int getTemplateType();
@Override
public int describeContents() {
return 0;
}
@Override
public final void writeToParcel(Parcel dest, int flags) {
dest.writeInt(getTemplateType());
dest.writeBundle(getDataBundle());
}
/**
* Obtain a {@link Bundle} describing this object populated with data.
* @return a {@link Bundle} containing the data that represents this object.
* @hide
*/
@CallSuper
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = new Bundle();
b.putInt(KEY_TEMPLATE_TYPE, getTemplateType());
b.putString(KEY_TEMPLATE_ID, mTemplateId);
return b;
}
@@ -148,6 +153,10 @@ public abstract class ControlTemplate implements Parcelable {
mTemplateId = "";
}
/**
* @param b
* @hide
*/
ControlTemplate(@NonNull Bundle b) {
mTemplateId = b.getString(KEY_TEMPLATE_ID);
}
@@ -160,48 +169,46 @@ public abstract class ControlTemplate implements Parcelable {
mTemplateId = templateId;
}
public static final Creator<ControlTemplate> CREATOR = new Creator<ControlTemplate>() {
@Override
public ControlTemplate createFromParcel(Parcel source) {
int type = source.readInt();
return createTemplateFromType(type, source);
/**
*
* @param bundle
* @return
* @hide
*/
@NonNull
static ControlTemplate createTemplateFromBundle(@Nullable Bundle bundle) {
if (bundle == null) {
Log.e(TAG, "Null bundle");
return ERROR_TEMPLATE;
}
@Override
public ControlTemplate[] newArray(int size) {
return new ControlTemplate[size];
}
};
private static ControlTemplate createTemplateFromType(@TemplateType int type, Parcel source) {
switch(type) {
case TYPE_TOGGLE:
return new ToggleTemplate(source.readBundle());
case TYPE_RANGE:
return new RangeTemplate(source.readBundle());
case TYPE_THUMBNAIL:
return new ThumbnailTemplate(source.readBundle());
case TYPE_DISCRETE_TOGGLE:
return new DiscreteToggleTemplate(source.readBundle());
case TYPE_COORD_RANGE:
return new CoordinatedRangeTemplate(source.readBundle());
case TYPE_TOGGLE_RANGE:
return new ToggleRangeTemplate(source.readBundle());
case TYPE_TEMPERATURE:
return new TemperatureControlTemplate(source.readBundle());
case TYPE_STATELESS:
return new StatelessTemplate(source.readBundle());
case TYPE_NONE:
default:
source.readBundle();
return NO_TEMPLATE;
}
}
protected static void verifyType(@TemplateType int type, @TemplateType int thisType) {
if (type != thisType) {
throw new IllegalStateException("The type " + type + "does not match " + thisType);
int type = bundle.getInt(KEY_TEMPLATE_TYPE, TYPE_ERROR);
try {
switch (type) {
case TYPE_TOGGLE:
return new ToggleTemplate(bundle);
case TYPE_RANGE:
return new RangeTemplate(bundle);
case TYPE_THUMBNAIL:
return new ThumbnailTemplate(bundle);
case TYPE_DISCRETE_TOGGLE:
return new DiscreteToggleTemplate(bundle);
case TYPE_COORD_RANGE:
return new CoordinatedRangeTemplate(bundle);
case TYPE_TOGGLE_RANGE:
return new ToggleRangeTemplate(bundle);
case TYPE_TEMPERATURE:
return new TemperatureControlTemplate(bundle);
case TYPE_STATELESS:
return new StatelessTemplate(bundle);
case TYPE_NONE:
return NO_TEMPLATE;
case TYPE_ERROR:
default:
return ERROR_TEMPLATE;
}
} catch (Exception e) {
Log.e(TAG, "Error creating template", e);
return ERROR_TEMPLATE;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* 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.
@@ -16,4 +16,4 @@
package android.service.controls.templates;
parcelable StatelessTemplate;
parcelable ControlTemplateWrapper;

View File

@@ -0,0 +1,66 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Wrapper for parceling/unparceling {@link ControlTemplate}.
* @hide
*/
public final class ControlTemplateWrapper implements Parcelable {
private final @NonNull ControlTemplate mControlTemplate;
public ControlTemplateWrapper(@NonNull ControlTemplate template) {
Preconditions.checkNotNull(template);
mControlTemplate = template;
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public ControlTemplate getWrappedTemplate() {
return mControlTemplate;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBundle(mControlTemplate.getDataBundle());
}
public static final @NonNull Creator<ControlTemplateWrapper> CREATOR =
new Creator<ControlTemplateWrapper>() {
@Override
public ControlTemplateWrapper createFromParcel(@NonNull Parcel source) {
return new ControlTemplateWrapper(
ControlTemplate.createTemplateFromBundle(source.readBundle()));
}
@Override
public ControlTemplateWrapper[] newArray(int size) {
return new ControlTemplateWrapper[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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;
parcelable CoordinatedRangeTemplate;

View File

@@ -19,12 +19,8 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
/**
* @hide
*/
public final class CoordinatedRangeTemplate extends ControlTemplate {
private static final String TAG = "CoordinatedRangeTemplate";
@@ -74,10 +70,14 @@ public final class CoordinatedRangeTemplate extends ControlTemplate {
minValueHigh, maxValueHigh, currentValueHigh, stepValue, formatString));
}
/**
* @param b
* @hide
*/
CoordinatedRangeTemplate(Bundle b) {
super(b);
mRangeLow = b.getParcelable(KEY_RANGE_LOW);
mRangeHigh = b.getParcelable(KEY_RANGE_HIGH);
mRangeLow = new RangeTemplate(b.getBundle(KEY_RANGE_LOW));
mRangeHigh = new RangeTemplate(b.getBundle(KEY_RANGE_HIGH));
mMinGap = b.getFloat(KEY_MIN_GAP);
validateRanges();
}
@@ -134,11 +134,16 @@ public final class CoordinatedRangeTemplate extends ControlTemplate {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putParcelable(KEY_RANGE_LOW, mRangeLow);
b.putParcelable(KEY_RANGE_HIGH, mRangeHigh);
b.putBundle(KEY_RANGE_LOW, mRangeLow.getDataBundle());
b.putBundle(KEY_RANGE_HIGH, mRangeHigh.getDataBundle());
return b;
}
@@ -160,18 +165,4 @@ public final class CoordinatedRangeTemplate extends ControlTemplate {
}
}
public static final Creator<CoordinatedRangeTemplate> CREATOR =
new Creator<CoordinatedRangeTemplate>() {
@Override
public CoordinatedRangeTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new CoordinatedRangeTemplate(source.readBundle());
}
@Override
public CoordinatedRangeTemplate[] newArray(int size) {
return new CoordinatedRangeTemplate[size];
}
};
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright (C) 2019 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;
parcelable DiscreteToggleTemplate;

View File

@@ -18,7 +18,6 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Parcel;
import android.service.controls.Control;
import android.service.controls.actions.BooleanAction;
@@ -33,9 +32,8 @@ import com.android.internal.util.Preconditions;
* {@link BooleanAction#getNewState} will be {@code false} if the button was
* {@link DiscreteToggleTemplate#getNegativeButton} and {@code true} if the button was
* {@link DiscreteToggleTemplate#getPositiveButton}.
* @hide
*/
public class DiscreteToggleTemplate extends ControlTemplate {
public final class DiscreteToggleTemplate extends ControlTemplate {
private static final @TemplateType int TYPE = TYPE_DISCRETE_TOGGLE;
private static final String KEY_NEGATIVE_BUTTON = "key_negative_button";
@@ -46,8 +44,8 @@ public class DiscreteToggleTemplate extends ControlTemplate {
/**
* @param templateId the identifier for this template object
* @param negativeButton a {@ControlButton} for the <i>Negative</i> input
* @param positiveButton a {@ControlButton} for the <i>Positive</i> input
* @param negativeButton a {@link ControlButton} for the <i>Negative</i> input
* @param positiveButton a {@link ControlButton} for the <i>Positive</i> input
*/
public DiscreteToggleTemplate(@NonNull String templateId,
@NonNull ControlButton negativeButton,
@@ -59,6 +57,10 @@ public class DiscreteToggleTemplate extends ControlTemplate {
mPositiveButton = positiveButton;
}
/**
* @param b
* @hide
*/
DiscreteToggleTemplate(Bundle b) {
super(b);
mNegativeButton = b.getParcelable(KEY_NEGATIVE_BUTTON);
@@ -89,32 +91,17 @@ public class DiscreteToggleTemplate extends ControlTemplate {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
public int describeContents() {
return 0;
}
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putParcelable(KEY_NEGATIVE_BUTTON, mNegativeButton);
b.putParcelable(KEY_POSITIVE_BUTTON, mPositiveButton);
return b;
}
public static final Creator<DiscreteToggleTemplate> CREATOR =
new Creator<DiscreteToggleTemplate>() {
@Override
public DiscreteToggleTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new DiscreteToggleTemplate(source.readBundle());
}
@Override
public DiscreteToggleTemplate[] newArray(int size) {
return new DiscreteToggleTemplate[size];
}
};
}

View File

@@ -27,7 +27,6 @@ import android.service.controls.actions.FloatAction;
* A template for a {@link Control} with inputs in a "continuous" range of values.
*
* @see FloatAction
* @hide
*/
public final class RangeTemplate extends ControlTemplate {
@@ -148,8 +147,13 @@ public final class RangeTemplate extends ControlTemplate {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putFloat(KEY_MIN_VALUE, mMinValue);
b.putFloat(KEY_MAX_VALUE, mMaxValue);
@@ -181,18 +185,4 @@ public final class RangeTemplate extends ControlTemplate {
throw new IllegalArgumentException(String.format("stepValue=%f <= 0", mStepValue));
}
}
public static final Creator<RangeTemplate> CREATOR = new Creator<RangeTemplate>() {
@Override
public RangeTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new RangeTemplate(source.readBundle());
}
@Override
public RangeTemplate[] newArray(int size) {
return new RangeTemplate[size];
}
};
}

View File

@@ -18,11 +18,7 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Parcel;
/**
* @hide
*/
public final class StatelessTemplate extends ControlTemplate {
@Override
@@ -30,23 +26,15 @@ public final class StatelessTemplate extends ControlTemplate {
return TYPE_STATELESS;
}
public StatelessTemplate(@NonNull Bundle b) {
/**
* @param b
* @hide
*/
StatelessTemplate(@NonNull Bundle b) {
super(b);
}
public StatelessTemplate(@NonNull String templateId) {
super(templateId);
}
public static final Creator<StatelessTemplate> CREATOR = new Creator<StatelessTemplate>() {
@Override
public StatelessTemplate createFromParcel(Parcel source) {
return new StatelessTemplate(source.readBundle());
}
@Override
public StatelessTemplate[] newArray(int size) {
return new StatelessTemplate[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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;
parcelable TemperatureControlTemplate;

View File

@@ -19,7 +19,6 @@ package android.service.controls.templates;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -27,9 +26,6 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @hide
*/
public final class TemperatureControlTemplate extends ControlTemplate {
private static final String TAG = "ThermostatTemplate";
@@ -67,6 +63,9 @@ public final class TemperatureControlTemplate extends ControlTemplate {
public static final @Mode int MODE_ECO = 5;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
FLAG_MODE_OFF,
@@ -136,18 +135,27 @@ public final class TemperatureControlTemplate extends ControlTemplate {
}
}
/**
* @param b
* @hide
*/
TemperatureControlTemplate(@NonNull Bundle b) {
super(b);
mTemplate = b.getParcelable(KEY_TEMPLATE);
mTemplate = ControlTemplate.createTemplateFromBundle(b.getBundle(KEY_TEMPLATE));
mCurrentMode = b.getInt(KEY_CURRENT_MODE);
mCurrentActiveMode = b.getInt(KEY_CURRENT_ACTIVE_MODE);
mModes = b.getInt(KEY_MODES);
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putParcelable(KEY_TEMPLATE, mTemplate);
b.putBundle(KEY_TEMPLATE, mTemplate.getDataBundle());
b.putInt(KEY_CURRENT_MODE, mCurrentMode);
b.putInt(KEY_CURRENT_ACTIVE_MODE, mCurrentActiveMode);
b.putInt(KEY_MODES, mModes);
@@ -175,18 +183,4 @@ public final class TemperatureControlTemplate extends ControlTemplate {
public int getTemplateType() {
return TYPE;
}
public static final Creator<TemperatureControlTemplate> CREATOR = new Creator<TemperatureControlTemplate>() {
@Override
public TemperatureControlTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new TemperatureControlTemplate(source.readBundle());
}
@Override
public TemperatureControlTemplate[] newArray(int size) {
return new TemperatureControlTemplate[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (c) 2019, 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;
parcelable ThumbnailTemplate;

View File

@@ -19,15 +19,12 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
import android.service.controls.Control;
import com.android.internal.util.Preconditions;
/**
* A template for a {@link Control} that displays an image.
*
* @hide
*/
public final class ThumbnailTemplate extends ControlTemplate {
@@ -52,6 +49,10 @@ public final class ThumbnailTemplate extends ControlTemplate {
mContentDescription = contentDescription;
}
/**
* @param b
* @hide
*/
ThumbnailTemplate(Bundle b) {
super(b);
mThumbnail = b.getParcelable(KEY_ICON);
@@ -82,25 +83,16 @@ public final class ThumbnailTemplate extends ControlTemplate {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putObject(KEY_ICON, mThumbnail);
b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
return b;
}
public static final Creator<ThumbnailTemplate> CREATOR = new Creator<ThumbnailTemplate>() {
@Override
public ThumbnailTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new ThumbnailTemplate(source.readBundle());
}
@Override
public ThumbnailTemplate[] newArray(int size) {
return new ThumbnailTemplate[size];
}
};
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2019 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;
parcelable ToggleRangeTemplate;

View File

@@ -18,13 +18,9 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Parcel;
import com.android.internal.util.Preconditions;
/**
* @hide
*/
public final class ToggleRangeTemplate extends ControlTemplate {
private static final @TemplateType int TYPE = TYPE_TOGGLE_RANGE;
@@ -34,11 +30,14 @@ public final class ToggleRangeTemplate extends ControlTemplate {
private @NonNull final ControlButton mControlButton;
private @NonNull final RangeTemplate mRangeTemplate;
/**
* @param b
* @hide
*/
ToggleRangeTemplate(@NonNull Bundle b) {
super(b);
mControlButton = b.getParcelable(KEY_BUTTON);
mRangeTemplate = b.getParcelable(KEY_RANGE);
mRangeTemplate = new RangeTemplate(b.getBundle(KEY_RANGE));
}
public ToggleRangeTemplate(@NonNull String templateId,
@@ -60,11 +59,16 @@ public final class ToggleRangeTemplate extends ControlTemplate {
range);
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putParcelable(KEY_BUTTON, mControlButton);
b.putParcelable(KEY_RANGE, mRangeTemplate);
b.putBundle(KEY_RANGE, mRangeTemplate.getDataBundle());
return b;
}
@@ -86,19 +90,4 @@ public final class ToggleRangeTemplate extends ControlTemplate {
public int getTemplateType() {
return TYPE;
}
public static final Creator<ToggleRangeTemplate> CREATOR = new Creator<ToggleRangeTemplate>() {
@Override
public ToggleRangeTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new ToggleRangeTemplate(source.readBundle());
}
@Override
public ToggleRangeTemplate[] newArray(int size) {
return new ToggleRangeTemplate[size];
}
};
}

View File

@@ -18,7 +18,6 @@ package android.service.controls.templates;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Parcel;
import android.service.controls.Control;
import android.service.controls.actions.BooleanAction;
@@ -31,7 +30,6 @@ import com.android.internal.util.Preconditions;
* An action on this template will originate a {@link BooleanAction} to change that state.
*
* @see BooleanAction
* @hide
*/
public final class ToggleTemplate extends ControlTemplate {
@@ -41,7 +39,7 @@ public final class ToggleTemplate extends ControlTemplate {
/**
* @param templateId the identifier for this template object
* @param button a {@ControlButton} that can show the current state and toggle it
* @param button a {@link ControlButton} that can show the current state and toggle it
*/
public ToggleTemplate(@NonNull String templateId, @NonNull ControlButton button) {
super(templateId);
@@ -49,6 +47,10 @@ public final class ToggleTemplate extends ControlTemplate {
mButton = button;
}
/**
* @param b
* @hide
*/
ToggleTemplate(Bundle b) {
super(b);
mButton = b.getParcelable(KEY_BUTTON);
@@ -58,6 +60,7 @@ public final class ToggleTemplate extends ControlTemplate {
return mButton.isChecked();
}
@NonNull
public CharSequence getContentDescription() {
return mButton.getActionDescription();
}
@@ -70,25 +73,15 @@ public final class ToggleTemplate extends ControlTemplate {
return TYPE;
}
/**
* @return
* @hide
*/
@Override
protected Bundle getDataBundle() {
@NonNull
Bundle getDataBundle() {
Bundle b = super.getDataBundle();
b.putParcelable(KEY_BUTTON, mButton);
return b;
}
public static final Creator<ToggleTemplate> CREATOR = new Creator<ToggleTemplate>() {
@Override
public ToggleTemplate createFromParcel(Parcel source) {
int type = source.readInt();
verifyType(type, TYPE);
return new ToggleTemplate(source.readBundle());
}
@Override
public ToggleTemplate[] newArray(int size) {
return new ToggleTemplate[size];
}
};
}

View File

@@ -0,0 +1,242 @@
/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.IIntentSender;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ControlProviderServiceTest {
private IBinder mToken = new Binder();
@Mock
private IControlsActionCallback.Stub mActionCallback;
@Mock
private IControlsLoadCallback.Stub mLoadCallback;
@Mock
private IControlsSubscriber.Stub mSubscriber;
@Mock
private IIntentSender mIIntentSender;
private PendingIntent mPendingIntent;
private FakeControlsProviderService mControlsProviderService;
private IControlsProvider mControlsProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mActionCallback.asBinder()).thenCallRealMethod();
when(mActionCallback.queryLocalInterface(any())).thenReturn(mActionCallback);
when(mLoadCallback.asBinder()).thenCallRealMethod();
when(mLoadCallback.queryLocalInterface(any())).thenReturn(mLoadCallback);
when(mSubscriber.asBinder()).thenCallRealMethod();
when(mSubscriber.queryLocalInterface(any())).thenReturn(mSubscriber);
Bundle b = new Bundle();
b.putBinder(ControlsProviderService.CALLBACK_TOKEN, mToken);
Intent intent = new Intent();
intent.putExtra(ControlsProviderService.CALLBACK_BUNDLE, b);
mPendingIntent = new PendingIntent(mIIntentSender);
mControlsProviderService = new FakeControlsProviderService();
mControlsProvider = IControlsProvider.Stub.asInterface(
mControlsProviderService.onBind(intent));
}
@Test
public void testOnLoad_allStateless() throws RemoteException {
Control control1 = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build();
Control control2 = new Control.StatelessBuilder("TEST_ID_2", mPendingIntent)
.setDeviceType(DeviceTypes.TYPE_AIR_FRESHENER).build();
@SuppressWarnings("unchecked")
ArgumentCaptor<List<Control>> captor = ArgumentCaptor.forClass(List.class);
ArrayList<Control> list = new ArrayList<>();
list.add(control1);
list.add(control2);
mControlsProviderService.setControls(list);
mControlsProvider.load(mLoadCallback);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(mLoadCallback).accept(eq(mToken), captor.capture());
List<Control> l = captor.getValue();
assertEquals(2, l.size());
assertTrue(equals(control1, l.get(0)));
assertTrue(equals(control2, l.get(1)));
}
@Test
public void testOnLoad_statefulConvertedToStateless() throws RemoteException {
Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
.setTitle("TEST_TITLE")
.setStatus(Control.STATUS_OK)
.build();
Control statelessControl = new Control.StatelessBuilder(control).build();
@SuppressWarnings("unchecked")
ArgumentCaptor<List<Control>> captor = ArgumentCaptor.forClass(List.class);
ArrayList<Control> list = new ArrayList<>();
list.add(control);
mControlsProviderService.setControls(list);
mControlsProvider.load(mLoadCallback);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(mLoadCallback).accept(eq(mToken), captor.capture());
List<Control> l = captor.getValue();
assertEquals(1, l.size());
assertFalse(equals(control, l.get(0)));
assertTrue(equals(statelessControl, l.get(0)));
assertEquals(Control.STATUS_UNKNOWN, l.get(0).getStatus());
}
@Test
public void testSubscribe() throws RemoteException {
Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
.setTitle("TEST_TITLE")
.setStatus(Control.STATUS_OK)
.build();
@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());
Control c = controlCaptor.getValue();
assertTrue(equals(c, list.get(0)));
}
@Test
public void testOnAction() throws RemoteException {
mControlsProvider.action("TEST_ID", new ControlActionWrapper(
new CommandAction("", null)), mActionCallback);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(mActionCallback).accept(mToken, "TEST_ID",
ControlAction.RESPONSE_OK);
}
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;
return Objects.equals(c1.getControlId(), c2.getControlId())
&& c1.getDeviceType() == c2.getDeviceType()
&& Objects.equals(c1.getTitle(), c2.getTitle())
&& Objects.equals(c1.getSubtitle(), c2.getSubtitle())
&& Objects.equals(c1.getStructure(), c2.getStructure())
&& Objects.equals(c1.getZone(), c2.getZone())
&& Objects.equals(c1.getAppIntent(), c2.getAppIntent())
&& Objects.equals(c1.getCustomIcon(), c2.getCustomIcon())
&& Objects.equals(c1.getCustomColor(), c2.getCustomColor())
&& c1.getStatus() == c2.getStatus()
&& Objects.equals(c1.getControlTemplate(), c2.getControlTemplate())
&& Objects.equals(c1.getStatusText(), c2.getStatusText());
}
static class FakeControlsProviderService extends ControlsProviderService {
private List<Control> mControls;
public void setControls(List<Control> controls) {
mControls = controls;
}
@Override
public void loadAvailableControls(Consumer<List<Control>> cb) {
cb.accept(mControls);
}
@Override
public Publisher<Control> publisherFor(List<String> ids) {
return new Publisher<Control>() {
public void subscribe(final Subscriber s) {
s.onSubscribe(new Subscription() {
public void request(long n) {
for (Control c : mControls) {
s.onNext(c);
}
}
public void cancel() {}
});
}
};
}
@Override
public void performControlAction(String controlId, ControlAction action,
Consumer<Integer> cb) {
cb.accept(ControlAction.RESPONSE_OK);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.service.controls;
package android.service.controls.actions;
import static junit.framework.Assert.assertTrue;
@@ -22,12 +22,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.os.Parcel;
import android.service.controls.actions.BooleanAction;
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.FloatAction;
import android.service.controls.actions.ModeAction;
import android.service.controls.actions.MultiFloatAction;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -97,9 +91,9 @@ public class ControlActionTest {
assertNotNull(parcel);
parcel.setDataPosition(0);
toParcel.writeToParcel(parcel, 0);
new ControlActionWrapper(toParcel).writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return ControlAction.CREATOR.createFromParcel(parcel);
return ControlActionWrapper.CREATOR.createFromParcel(parcel).getWrappedAction();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.service.controls;
package android.service.controls.templates;
import static junit.framework.Assert.assertTrue;
@@ -24,16 +24,6 @@ import static org.junit.Assert.assertNotNull;
import android.annotation.DrawableRes;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.service.controls.templates.ControlButton;
import android.service.controls.templates.ControlTemplate;
import android.service.controls.templates.CoordinatedRangeTemplate;
import android.service.controls.templates.DiscreteToggleTemplate;
import android.service.controls.templates.RangeTemplate;
import android.service.controls.templates.StatelessTemplate;
import android.service.controls.templates.TemperatureControlTemplate;
import android.service.controls.templates.ThumbnailTemplate;
import android.service.controls.templates.ToggleRangeTemplate;
import android.service.controls.templates.ToggleTemplate;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -65,8 +55,7 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_none() {
ControlTemplate
toParcel = ControlTemplate.NO_TEMPLATE;
ControlTemplate toParcel = ControlTemplate.NO_TEMPLATE;
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -75,8 +64,7 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_toggle() {
ControlTemplate
toParcel = new android.service.controls.templates.ToggleTemplate(TEST_ID, mControlButton);
ControlTemplate toParcel = new ToggleTemplate(TEST_ID, mControlButton);
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -86,8 +74,7 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_range() {
ControlTemplate
toParcel = new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f");
ControlTemplate toParcel = new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f");
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -117,8 +104,7 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_thumbnail() {
ControlTemplate
toParcel = new ThumbnailTemplate(TEST_ID, mIcon, TEST_ACTION_DESCRIPTION);
ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, mIcon, TEST_ACTION_DESCRIPTION);
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -140,7 +126,7 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_coordRange() {
ControlTemplate toParcel =
new CoordinatedRangeTemplate(TEST_ID,0.1f, 0, 1, 0.5f, 1, 2, 1.5f, 0.1f, "%f");
new CoordinatedRangeTemplate(TEST_ID, 0.1f, 0, 1, 0.5f, 1, 2, 1.5f, 0.1f, "%f");
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
assertEquals(ControlTemplate.TYPE_COORD_RANGE, fromParcel.getTemplateType());
assertTrue(fromParcel instanceof CoordinatedRangeTemplate);
@@ -149,7 +135,7 @@ public class ControlTemplateTest {
@Test
public void testCoordRangeParameters_negativeMinGap() {
CoordinatedRangeTemplate template =
new CoordinatedRangeTemplate(TEST_ID,-0.1f, 0, 1, 0.5f, 1, 2, 1.5f, 0.1f, "%f");
new CoordinatedRangeTemplate(TEST_ID, -0.1f, 0, 1, 0.5f, 1, 2, 1.5f, 0.1f, "%f");
assertEquals(0, template.getMinGap(), 0);
}
@@ -176,9 +162,8 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_toggleRange() {
ControlTemplate toParcel =
new ToggleRangeTemplate(TEST_ID, mControlButton,
new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,
new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -198,11 +183,12 @@ public class ControlTemplateTest {
@Test
public void testUnparcelingCorrectClass_thermostat() {
ControlTemplate toParcel = new TemperatureControlTemplate(TEST_ID,
new ToggleTemplate("", mControlButton),
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_OFF);
ControlTemplate toParcel = new TemperatureControlTemplate(
TEST_ID,
new ToggleTemplate("", mControlButton),
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_OFF);
ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
@@ -212,48 +198,70 @@ public class ControlTemplateTest {
@Test
public void testThermostatParams_wrongMode() {
TemperatureControlTemplate thermostat = new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE, -1,
TemperatureControlTemplate.MODE_OFF, TemperatureControlTemplate.FLAG_MODE_OFF);
TemperatureControlTemplate thermostat = new TemperatureControlTemplate(
TEST_ID,
ControlTemplate.NO_TEMPLATE,
-1,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_OFF);
assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentMode());
thermostat = new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE, 100,
TemperatureControlTemplate.MODE_OFF, TemperatureControlTemplate.FLAG_MODE_OFF);
thermostat = new TemperatureControlTemplate(
TEST_ID,
ControlTemplate.NO_TEMPLATE,
100,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_OFF);
assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentMode());
}
@Test
public void testThermostatParams_wrongActiveMode() {
TemperatureControlTemplate thermostat = new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_OFF,-1, TemperatureControlTemplate.FLAG_MODE_OFF);
TemperatureControlTemplate thermostat = new TemperatureControlTemplate(
TEST_ID,
ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_OFF,
-1,
TemperatureControlTemplate.FLAG_MODE_OFF);
assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentActiveMode());
thermostat = new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_OFF,100, TemperatureControlTemplate.FLAG_MODE_OFF);
thermostat = new TemperatureControlTemplate(
TEST_ID,
ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_OFF,
100,
TemperatureControlTemplate.FLAG_MODE_OFF);
assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentActiveMode());
}
@Test(expected = IllegalArgumentException.class)
public void testThermostatParams_wrongFlags_currentMode() {
new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE, TemperatureControlTemplate.MODE_HEAT,
TemperatureControlTemplate.MODE_OFF, TemperatureControlTemplate.FLAG_MODE_OFF);
new TemperatureControlTemplate(
TEST_ID,
ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_HEAT,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_OFF);
}
@Test(expected = IllegalArgumentException.class)
public void testThermostatParams_wrongFlags_currentActiveMode() {
new TemperatureControlTemplate(TEST_ID, ControlTemplate.NO_TEMPLATE, TemperatureControlTemplate.MODE_HEAT,
TemperatureControlTemplate.MODE_OFF, TemperatureControlTemplate.FLAG_MODE_HEAT);
new TemperatureControlTemplate(TEST_ID,
ControlTemplate.NO_TEMPLATE,
TemperatureControlTemplate.MODE_HEAT,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_HEAT);
}
private ControlTemplate parcelAndUnparcel(
ControlTemplate toParcel) {
private ControlTemplate parcelAndUnparcel(ControlTemplate toParcel) {
Parcel parcel = Parcel.obtain();
assertNotNull(parcel);
parcel.setDataPosition(0);
toParcel.writeToParcel(parcel, 0);
new ControlTemplateWrapper(toParcel).writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return ControlTemplate.CREATOR.createFromParcel(parcel);
return ControlTemplateWrapper.CREATOR.createFromParcel(parcel).getWrappedTemplate();
}
}

View File

@@ -20,7 +20,10 @@ import android.content.ComponentName
import android.content.Context
import android.os.IBinder
import android.service.controls.Control
import android.service.controls.IControlsProviderCallback
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlAction
import android.util.ArrayMap
import android.util.Log
@@ -54,20 +57,14 @@ open class ControlsBindingControllerImpl @Inject constructor(
private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
private val serviceCallback = object : IControlsProviderCallback.Stub() {
override fun onLoad(token: IBinder, controls: MutableList<Control>) {
private val loadCallbackService = object : IControlsLoadCallback.Stub() {
override fun accept(token: IBinder, controls: MutableList<Control>) {
backgroundExecutor.execute(OnLoadRunnable(token, controls))
}
}
override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
if (!refreshing.get()) {
Log.d(TAG, "Refresh outside of window for token:$token")
} else {
backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
}
}
override fun onControlActionResponse(
private val actionCallbackService = object : IControlsActionCallback.Stub() {
override fun accept(
token: IBinder,
controlId: String,
@ControlAction.ResponseResult response: Int
@@ -76,13 +73,36 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
}
private val subscriberService = object : IControlsSubscriber.Stub() {
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
}
override fun onNext(token: IBinder, c: Control) {
if (!refreshing.get()) {
Log.d(TAG, "Refresh outside of window for token:$token")
} else {
backgroundExecutor.execute(OnNextRunnable(token, c))
}
}
override fun onError(token: IBinder, s: String) {
backgroundExecutor.execute(OnErrorRunnable(token, s))
}
override fun onComplete(token: IBinder) {
backgroundExecutor.execute(OnCompleteRunnable(token))
}
}
@VisibleForTesting
internal open fun createProviderManager(component: ComponentName):
ControlsProviderLifecycleManager {
return ControlsProviderLifecycleManager(
context,
backgroundExecutor,
serviceCallback,
loadCallbackService,
actionCallbackService,
subscriberService,
component
)
}
@@ -176,16 +196,51 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
}
private inner class OnRefreshStateRunnable(
private inner class OnNextRunnable(
token: IBinder,
val list: List<Control>
val control: Control
) : CallbackRunnable(token) {
override fun run() {
if (!refreshing.get()) {
Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
}
provider?.let {
lazyController.get().refreshStatus(it.componentName, list)
lazyController.get().refreshStatus(it.componentName, control)
}
}
}
private inner class OnSubscribeRunnable(
token: IBinder,
val subscription: IControlsSubscription
) : CallbackRunnable(token) {
override fun run() {
if (!refreshing.get()) {
Log.d(TAG, "onRefresh outside of window from '${provider?.componentName}'")
}
provider?.let {
it.startSubscription(subscription)
}
}
}
private inner class OnCompleteRunnable(
token: IBinder
) : CallbackRunnable(token) {
override fun run() {
provider?.let {
Log.i(TAG, "onComplete receive from '${provider?.componentName}'")
}
}
}
private inner class OnErrorRunnable(
token: IBinder,
val error: String
) : CallbackRunnable(token) {
override fun run() {
provider?.let {
Log.e(TAG, "onError receive from '${provider?.componentName}': $error")
}
}
}
@@ -201,4 +256,4 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
}
}
}
}

View File

@@ -30,11 +30,11 @@ interface ControlsController {
fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
fun unsubscribe()
fun action(controlInfo: ControlInfo, action: ControlAction)
fun refreshStatus(componentName: ComponentName, controls: List<Control>)
fun refreshStatus(componentName: ComponentName, control: Control)
fun onActionResponse(
componentName: ComponentName,
controlId: String,
@ControlAction.ResponseResult response: Int
)
fun clearFavorites()
}
}

View File

@@ -210,20 +210,20 @@ class ControlsControllerImpl @Inject constructor (
}
}
override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
override fun refreshStatus(componentName: ComponentName, control: Control) {
if (!available) {
Log.d(TAG, "Controls not available")
return
}
executor.execute {
synchronized(currentFavorites) {
val changed = updateFavoritesLocked(componentName, controls)
val changed = updateFavoritesLocked(componentName, listOf(control))
if (changed) {
persistenceWrapper.storeFavorites(favoritesAsListLocked())
}
}
}
uiController.onRefreshState(componentName, controls)
uiController.onRefreshState(componentName, listOf(control))
}
override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
@@ -270,4 +270,4 @@ class ControlsControllerImpl @Inject constructor (
}
}
}
}
}

View File

@@ -25,11 +25,13 @@ import android.os.Bundle
import android.os.IBinder
import android.os.RemoteException
import android.service.controls.Control
import android.service.controls.ControlsProviderService.CALLBACK_BINDER
import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsProvider
import android.service.controls.IControlsProviderCallback
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlAction
import android.util.ArraySet
import android.util.Log
@@ -41,19 +43,23 @@ typealias LoadCallback = (List<Control>) -> Unit
class ControlsProviderLifecycleManager(
private val context: Context,
private val executor: DelayableExecutor,
private val serviceCallback: IControlsProviderCallback.Stub,
private val loadCallbackService: IControlsLoadCallback.Stub,
private val actionCallbackService: IControlsActionCallback.Stub,
private val subscriberService: IControlsSubscriber.Stub,
val componentName: ComponentName
) : IBinder.DeathRecipient {
var lastLoadCallback: LoadCallback? = null
private set
val token: IBinder = Binder()
@GuardedBy("subscriptions")
private val subscriptions = mutableListOf<IControlsSubscription>()
private var unbindImmediate = false
private var requiresBound = false
private var isBound = false
@GuardedBy("queuedMessages")
private val queuedMessages: MutableSet<Message> = ArraySet()
private var wrapper: ControlsProviderServiceWrapper? = null
private var wrapper: ServiceWrapper? = null
private var bindTryCount = 0
private val TAG = javaClass.simpleName
private var onLoadCanceller: Runnable? = null
@@ -61,12 +67,12 @@ class ControlsProviderLifecycleManager(
companion object {
private const val MSG_LOAD = 0
private const val MSG_SUBSCRIBE = 1
private const val MSG_UNSUBSCRIBE = 2
private const val MSG_ON_ACTION = 3
private const val MSG_UNBIND = 4
private const val MSG_ACTION = 2
private const val MSG_UNBIND = 3
private const val BIND_RETRY_DELAY = 1000L // ms
private const val LOAD_TIMEOUT = 5000L // ms
private const val MAX_BIND_RETRIES = 5
private const val MAX_CONTROLS_REQUEST = 100000L
private const val DEBUG = true
private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
Context.BIND_WAIVE_PRIORITY
@@ -75,7 +81,6 @@ class ControlsProviderLifecycleManager(
private val intent = Intent().apply {
component = componentName
putExtra(CALLBACK_BUNDLE, Bundle().apply {
putBinder(CALLBACK_BINDER, serviceCallback)
putBinder(CALLBACK_TOKEN, token)
})
}
@@ -119,7 +124,7 @@ class ControlsProviderLifecycleManager(
override fun onServiceConnected(name: ComponentName, service: IBinder) {
if (DEBUG) Log.d(TAG, "onServiceConnected $name")
bindTryCount = 0
wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service))
try {
service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
} catch (_: RemoteException) {}
@@ -151,7 +156,7 @@ class ControlsProviderLifecycleManager(
}
queue.filter { it is Message.Action }.forEach {
val msg = it as Message.Action
onAction(msg.id, msg.action)
action(msg.id, msg.action)
}
}
@@ -182,7 +187,7 @@ class ControlsProviderLifecycleManager(
if (DEBUG) {
Log.d(TAG, "load $componentName")
}
if (!(wrapper?.load() ?: false)) {
if (!(wrapper?.load(loadCallbackService) ?: false)) {
queueMessage(Message.Load)
binderDied()
}
@@ -194,7 +199,7 @@ class ControlsProviderLifecycleManager(
onLoadCanceller = executor.executeDelayed({
// Didn't receive a response in time, log and send back empty list
Log.d(TAG, "Timeout waiting onLoad for $componentName")
serviceCallback.onLoad(token, emptyList())
loadCallbackService.accept(token, emptyList())
}, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
if (isBound) {
load()
@@ -218,7 +223,7 @@ class ControlsProviderLifecycleManager(
if (DEBUG) {
Log.d(TAG, "subscribe $componentName - $controlIds")
}
if (!(wrapper?.subscribe(controlIds) ?: false)) {
if (!(wrapper?.subscribe(controlIds, subscriberService) ?: false)) {
queueMessage(Message.Subscribe(controlIds))
binderDied()
}
@@ -226,29 +231,45 @@ class ControlsProviderLifecycleManager(
fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
if (isBound) {
onAction(controlId, action)
action(controlId, action)
} else {
queueMessage(Message.Action(controlId, action))
bindService(true)
}
}
private fun onAction(controlId: String, action: ControlAction) {
private fun action(controlId: String, action: ControlAction) {
if (DEBUG) {
Log.d(TAG, "onAction $componentName - $controlId")
}
if (!(wrapper?.onAction(controlId, action) ?: false)) {
if (!(wrapper?.action(controlId, action, actionCallbackService) ?: false)) {
queueMessage(Message.Action(controlId, action))
binderDied()
}
}
fun startSubscription(subscription: IControlsSubscription) {
synchronized(subscriptions) {
subscriptions.add(subscription)
}
wrapper?.request(subscription, MAX_CONTROLS_REQUEST)
}
fun unsubscribe() {
if (DEBUG) {
Log.d(TAG, "unsubscribe $componentName")
}
unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
wrapper?.unsubscribe()
val subs = synchronized(subscriptions) {
ArrayList(subscriptions).also {
subscriptions.clear()
}
}
subs.forEach {
wrapper?.cancel(it)
}
}
fun maybeUnbindAndRemoveCallback() {
@@ -277,7 +298,7 @@ class ControlsProviderLifecycleManager(
override val type = MSG_SUBSCRIBE
}
class Action(val id: String, val action: ControlAction) : Message() {
override val type = MSG_ON_ACTION
override val type = MSG_ACTION
}
}
}
}

View File

@@ -17,12 +17,17 @@
package com.android.systemui.controls.controller
import android.service.controls.actions.ControlAction
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsProvider
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlActionWrapper
import android.util.Log
class ControlsProviderServiceWrapper(val service: IControlsProvider) {
class ServiceWrapper(val service: IControlsProvider) {
companion object {
private const val TAG = "ControlsProviderServiceWrapper"
private const val TAG = "ServiceWrapper"
}
private fun callThroughService(block: () -> Unit): Boolean {
@@ -30,32 +35,42 @@ class ControlsProviderServiceWrapper(val service: IControlsProvider) {
block()
return true
} catch (ex: Exception) {
Log.d(TAG, "Caught exception from ControlsProviderService", ex)
Log.e(TAG, "Caught exception from ControlsProviderService", ex)
return false
}
}
fun load(): Boolean {
fun load(cb: IControlsLoadCallback): Boolean {
return callThroughService {
service.load()
service.load(cb)
}
}
fun subscribe(controlIds: List<String>): Boolean {
fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber): Boolean {
return callThroughService {
service.subscribe(controlIds)
service.subscribe(controlIds, subscriber)
}
}
fun unsubscribe(): Boolean {
fun request(subscription: IControlsSubscription, num: Long): Boolean {
return callThroughService {
service.unsubscribe()
subscription.request(num)
}
}
fun onAction(controlId: String, action: ControlAction): Boolean {
fun cancel(subscription: IControlsSubscription): Boolean {
return callThroughService {
service.onAction(controlId, action)
subscription.cancel()
}
}
}
fun action(
controlId: String,
action: ControlAction,
cb: IControlsActionCallback
): Boolean {
return callThroughService {
service.action(controlId, ControlActionWrapper(action), cb)
}
}
}

View File

@@ -51,7 +51,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
context,
executor,
ServiceListing.Builder(context)
.setIntentAction(ControlsProviderService.CONTROLS_ACTION)
.setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
.setPermission("android.permission.BIND_CONTROLS")
.setNoun("Controls Provider")
.setSetting("controls_providers")

View File

@@ -224,8 +224,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
@Test
fun testRefreshStatus() {
val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
controller.refreshStatus(TEST_COMPONENT, list)
val control = Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build()
val list = listOf(control)
controller.refreshStatus(TEST_COMPONENT, control)
verify(uiController).onRefreshState(TEST_COMPONENT, list)
}
@@ -340,7 +341,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
controller.refreshStatus(TEST_COMPONENT, listOf(control))
controller.refreshStatus(TEST_COMPONENT, control)
delayableExecutor.runAllReady()
@@ -357,4 +358,4 @@ class ControlsControllerImplTest : SysuiTestCase() {
controller.clearFavorites()
assertTrue(controller.getFavoriteControls().isEmpty())
}
}
}

View File

@@ -18,9 +18,12 @@ package com.android.systemui.controls.controller
import android.content.ComponentName
import android.service.controls.Control
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsProvider
import android.service.controls.IControlsProviderCallback
import android.service.controls.IControlsSubscriber
import android.service.controls.actions.ControlAction
import android.service.controls.actions.ControlActionWrapper
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,7 +38,10 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
@@ -46,14 +52,25 @@ import org.mockito.MockitoAnnotations
class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
@Mock
private lateinit var serviceCallback: IControlsProviderCallback.Stub
private lateinit var actionCallback: IControlsActionCallback.Stub
@Mock
private lateinit var loadCallback: IControlsLoadCallback.Stub
@Mock
private lateinit var subscriber: IControlsSubscriber.Stub
@Mock
private lateinit var service: IControlsProvider.Stub
@Captor
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
private val componentName = ComponentName("test.pkg", "test.cls")
private lateinit var manager: ControlsProviderLifecycleManager
private lateinit var executor: DelayableExecutor
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
}
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -66,7 +83,9 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
manager = ControlsProviderLifecycleManager(
context,
executor,
serviceCallback,
loadCallback,
actionCallback,
subscriber,
componentName
)
}
@@ -94,7 +113,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
val callback: (List<Control>) -> Unit = {}
manager.maybeBindAndLoad(callback)
verify(service).load()
verify(service).load(loadCallback)
assertTrue(mContext.isBound(componentName))
assertEquals(callback, manager.lastLoadCallback)
@@ -109,30 +128,24 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
assertNull(manager.lastLoadCallback)
}
@Test
fun testUnsubscribe() {
manager.bindPermanently()
manager.unsubscribe()
verify(service).unsubscribe()
}
@Test
fun testMaybeBindAndSubscribe() {
val list = listOf("TEST_ID")
manager.maybeBindAndSubscribe(list)
assertTrue(mContext.isBound(componentName))
verify(service).subscribe(list)
verify(service).subscribe(list, subscriber)
}
@Test
fun testMaybeBindAndAction() {
val controlId = "TEST_ID"
val action = ControlAction.UNKNOWN_ACTION
val action = ControlAction.ERROR_ACTION
manager.maybeBindAndSendAction(controlId, action)
assertTrue(mContext.isBound(componentName))
verify(service).onAction(controlId, action)
verify(service).action(eq(controlId), capture(wrapperCaptor),
eq(actionCallback))
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
}
}
}

View File

@@ -1,127 +0,0 @@
/*
* 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 com.android.systemui.controls.controller
import android.os.RemoteException
import android.service.controls.IControlsProvider
import android.service.controls.actions.ControlAction
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsProviderServiceWrapperTest : SysuiTestCase() {
@Mock
private lateinit var service: IControlsProvider
private val exception = RemoteException()
private lateinit var wrapper: ControlsProviderServiceWrapper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
wrapper = ControlsProviderServiceWrapper(service)
}
@Test
fun testLoad_happyPath() {
val result = wrapper.load()
assertTrue(result)
verify(service).load()
}
@Test
fun testLoad_error() {
`when`(service.load()).thenThrow(exception)
val result = wrapper.load()
assertFalse(result)
}
@Test
fun testSubscribe_happyPath() {
val list = listOf("TEST_ID")
val result = wrapper.subscribe(list)
assertTrue(result)
verify(service).subscribe(list)
}
@Test
fun testSubscribe_error() {
`when`(service.subscribe(any())).thenThrow(exception)
val list = listOf("TEST_ID")
val result = wrapper.subscribe(list)
assertFalse(result)
}
@Test
fun testUnsubscribe_happyPath() {
val result = wrapper.unsubscribe()
assertTrue(result)
verify(service).unsubscribe()
}
@Test
fun testUnsubscribe_error() {
`when`(service.unsubscribe()).thenThrow(exception)
val result = wrapper.unsubscribe()
assertFalse(result)
}
@Test
fun testOnAction_happyPath() {
val id = "TEST_ID"
val action = ControlAction.UNKNOWN_ACTION
val result = wrapper.onAction(id, action)
assertTrue(result)
verify(service).onAction(id, action)
}
@Test
fun testOnAction_error() {
`when`(service.onAction(any(), any())).thenThrow(exception)
val id = "TEST_ID"
val action = ControlAction.UNKNOWN_ACTION
val result = wrapper.onAction(id, action)
assertFalse(result)
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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 com.android.systemui.controls.controller
import android.os.RemoteException
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsProvider
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlAction
import android.service.controls.actions.ControlActionWrapper
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ServiceWrapperTest : SysuiTestCase() {
@Mock
private lateinit var service: IControlsProvider
@Mock
private lateinit var subscription: IControlsSubscription
@Mock
private lateinit var subscriber: IControlsSubscriber
@Mock
private lateinit var loadCallback: IControlsLoadCallback
@Mock
private lateinit var actionCallback: IControlsActionCallback
@Captor
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
private val exception = RemoteException()
private lateinit var wrapper: ServiceWrapper
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
}
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
wrapper = ServiceWrapper(service)
}
@Test
fun testLoad_happyPath() {
val result = wrapper.load(loadCallback)
assertTrue(result)
verify(service).load(loadCallback)
}
@Test
fun testLoad_error() {
`when`(service.load(any())).thenThrow(exception)
val result = wrapper.load(loadCallback)
assertFalse(result)
}
@Test
fun testSubscribe_happyPath() {
val list = listOf("TEST_ID")
val result = wrapper.subscribe(list, subscriber)
assertTrue(result)
verify(service).subscribe(list, subscriber)
}
@Test
fun testSubscribe_error() {
`when`(service.subscribe(any(), any())).thenThrow(exception)
val list = listOf("TEST_ID")
val result = wrapper.subscribe(list, subscriber)
assertFalse(result)
}
@Test
fun testCancel_happyPath() {
val result = wrapper.cancel(subscription)
assertTrue(result)
verify(subscription).cancel()
}
@Test
fun testCancel_error() {
`when`(subscription.cancel()).thenThrow(exception)
val result = wrapper.cancel(subscription)
assertFalse(result)
}
@Test
fun testOnAction_happyPath() {
val id = "TEST_ID"
val action = ControlAction.ERROR_ACTION
val result = wrapper.action(id, action, actionCallback)
assertTrue(result)
verify(service).action(eq(id), capture(wrapperCaptor),
eq(actionCallback))
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
}
@Test
fun testOnAction_error() {
`when`(service.action(any(), any(), any())).thenThrow(exception)
val id = "TEST_ID"
val action = ControlAction.ERROR_ACTION
val result = wrapper.action(id, action, actionCallback)
assertFalse(result)
}
}