diff --git a/api/current.txt b/api/current.txt index 137ddcc462b92..576cb2b892c3d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -43006,6 +43006,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 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>); + 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); + method @NonNull public abstract java.util.concurrent.Flow.Publisher publisherFor(@NonNull java.util.List); + 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 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 { diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java index 43a308cf82cb8..2d1d0ede62ca5 100644 --- a/core/java/android/service/controls/Control.java +++ b/core/java/android/service/controls/Control.java @@ -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; *

* 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 CREATOR = new Creator() { + public static final @NonNull Creator CREATOR = new Creator() { @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 { *

  • Subtitle: {@code ""} * * This fixes the values relating to state of the {@link Control} as required by - * {@link ControlsProviderService#onLoad}: + * {@link ControlsProviderService#loadAvailableControls}: *
      *
    • Status: {@link Status#STATUS_UNKNOWN} *
    • Control template: {@link ControlTemplate#NO_TEMPLATE} *
    • Status text: {@code ""} *
    */ - 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: + *
      + *
    • Device type: {@link DeviceTypes#TYPE_UNKNOWN} + *
    • Title: {@code ""} + *
    • Subtitle: {@code ""} + *
    • Status: {@link Status#STATUS_UNKNOWN} + *
    • Control template: {@link ControlTemplate#NO_TEMPLATE} + *
    • Status text: {@code ""} + *
    + */ + 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); + } } } diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index eca8541c9d33e..bc65818870481 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -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> 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 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 publisherFor(@NonNull List 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 controls) { - Preconditions.checkNotNull(controls); - List 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 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 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 ids) { - mHandler.obtainMessage(RequestHandler.MSG_SUBSCRIBE, ids).sendToTarget(); + public void subscribe(List 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 ids = (List) msg.obj; - ControlsProviderService.this.subscribe(ids); + final SubscribeMessage sMsg = (SubscribeMessage) msg.obj; + final IControlsSubscriber cs = sMsg.mSubscriber; + Subscriber s = new Subscriber() { + 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 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> consumerFor(IControlsLoadCallback cb) { + return (@NonNull List controls) -> { + Preconditions.checkNotNull(controls); + List 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 mControlIds; + final IControlsSubscriber mSubscriber; + + SubscribeMessage(List controlIds, IControlsSubscriber subscriber) { + this.mControlIds = controlIds; + this.mSubscriber = subscriber; } } } diff --git a/core/java/android/service/controls/DeviceTypes.java b/core/java/android/service/controls/DeviceTypes.java index b2d1c08d65ba2..8dbb9cf8f9f7f 100644 --- a/core/java/android/service/controls/DeviceTypes.java +++ b/core/java/android/service/controls/DeviceTypes.java @@ -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 diff --git a/core/java/android/service/controls/templates/RangeTemplate.aidl b/core/java/android/service/controls/IControlsActionCallback.aidl similarity index 72% rename from core/java/android/service/controls/templates/RangeTemplate.aidl rename to core/java/android/service/controls/IControlsActionCallback.aidl index 9928815529035..eab4c89cb8e6f 100644 --- a/core/java/android/service/controls/templates/RangeTemplate.aidl +++ b/core/java/android/service/controls/IControlsActionCallback.aidl @@ -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; \ No newline at end of file +/** + * @hide + */ +oneway interface IControlsActionCallback { + void accept(in IBinder token, in String controlId, int response); +} \ No newline at end of file diff --git a/core/java/android/service/controls/templates/ToggleTemplate.aidl b/core/java/android/service/controls/IControlsLoadCallback.aidl similarity index 69% rename from core/java/android/service/controls/templates/ToggleTemplate.aidl rename to core/java/android/service/controls/IControlsLoadCallback.aidl index 98a9e49b76641..bfc61cdb54dba 100644 --- a/core/java/android/service/controls/templates/ToggleTemplate.aidl +++ b/core/java/android/service/controls/IControlsLoadCallback.aidl @@ -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; \ No newline at end of file +import android.service.controls.Control; + +/** + * @hide + */ +oneway interface IControlsLoadCallback { + void accept(in IBinder token, in List controls); +} \ No newline at end of file diff --git a/core/java/android/service/controls/IControlsProvider.aidl b/core/java/android/service/controls/IControlsProvider.aidl index 6c105bb20c80d..4ce658ed6990f 100644 --- a/core/java/android/service/controls/IControlsProvider.aidl +++ b/core/java/android/service/controls/IControlsProvider.aidl @@ -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 controlIds); + void subscribe(in List controlIds, + IControlsSubscriber subscriber); - void unsubscribe(); - - void onAction(in String controlId, in ControlAction action); + void action(in String controlId, in ControlActionWrapper action, + IControlsActionCallback cb); } \ No newline at end of file diff --git a/core/java/android/service/controls/IControlsProviderCallback.aidl b/core/java/android/service/controls/IControlsSubscriber.aidl similarity index 63% rename from core/java/android/service/controls/IControlsProviderCallback.aidl rename to core/java/android/service/controls/IControlsSubscriber.aidl index 91f6a7980ce63..75ce584c53211 100644 --- a/core/java/android/service/controls/IControlsProviderCallback.aidl +++ b/core/java/android/service/controls/IControlsSubscriber.aidl @@ -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 controls); - - void onRefreshState(in IBinder token, in List 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); } \ No newline at end of file diff --git a/core/java/android/service/controls/templates/ControlTemplate.aidl b/core/java/android/service/controls/IControlsSubscription.aidl similarity index 74% rename from core/java/android/service/controls/templates/ControlTemplate.aidl rename to core/java/android/service/controls/IControlsSubscription.aidl index b6ab28026a8df..0af575e07b5eb 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.aidl +++ b/core/java/android/service/controls/IControlsSubscription.aidl @@ -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; \ No newline at end of file +/** + * @hide + */ +oneway interface IControlsSubscription { + void request(long n); + void cancel(); +} \ No newline at end of file diff --git a/core/java/android/service/controls/actions/BooleanAction.aidl b/core/java/android/service/controls/actions/BooleanAction.aidl deleted file mode 100644 index d1e7e02730cc4..0000000000000 --- a/core/java/android/service/controls/actions/BooleanAction.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/BooleanAction.java b/core/java/android/service/controls/actions/BooleanAction.java index fb2c5ad996595..02593353bfc89 100644 --- a/core/java/android/service/controls/actions/BooleanAction.java +++ b/core/java/android/service/controls/actions/BooleanAction.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/actions/CommandAction.aidl b/core/java/android/service/controls/actions/CommandAction.aidl deleted file mode 100644 index 7c1ee41fb8c3e..0000000000000 --- a/core/java/android/service/controls/actions/CommandAction.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/CommandAction.java b/core/java/android/service/controls/actions/CommandAction.java index c69c539763493..84d60805c3e91 100644 --- a/core/java/android/service/controls/actions/CommandAction.java +++ b/core/java/android/service/controls/actions/CommandAction.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/actions/ControlAction.aidl b/core/java/android/service/controls/actions/ControlAction.aidl deleted file mode 100644 index b012521c72555..0000000000000 --- a/core/java/android/service/controls/actions/ControlAction.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/ControlAction.java b/core/java/android/service/controls/actions/ControlAction.java index 83d1cf824f1fc..4141da805b5a2 100644 --- a/core/java/android/service/controls/actions/ControlAction.java +++ b/core/java/android/service/controls/actions/ControlAction.java @@ -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 CREATOR = new Creator() { - @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); - } - } - } diff --git a/core/java/android/service/controls/actions/FloatAction.aidl b/core/java/android/service/controls/actions/ControlActionWrapper.aidl similarity index 87% rename from core/java/android/service/controls/actions/FloatAction.aidl rename to core/java/android/service/controls/actions/ControlActionWrapper.aidl index 2c1e76d688030..5ba962d822ba8 100644 --- a/core/java/android/service/controls/actions/FloatAction.aidl +++ b/core/java/android/service/controls/actions/ControlActionWrapper.aidl @@ -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; \ No newline at end of file +parcelable ControlActionWrapper; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/ControlActionWrapper.java b/core/java/android/service/controls/actions/ControlActionWrapper.java new file mode 100644 index 0000000000000..6a3ec86835ed8 --- /dev/null +++ b/core/java/android/service/controls/actions/ControlActionWrapper.java @@ -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 CREATOR = + new Creator() { + @Override + public ControlActionWrapper createFromParcel(@NonNull Parcel in) { + return new ControlActionWrapper( + ControlAction.createActionFromBundle(in.readBundle())); + } + + @Override + public ControlActionWrapper[] newArray(int size) { + return new ControlActionWrapper[size]; + } + }; +} diff --git a/core/java/android/service/controls/actions/FloatAction.java b/core/java/android/service/controls/actions/FloatAction.java index 1c3fb4d39744e..5b271ce5c577a 100644 --- a/core/java/android/service/controls/actions/FloatAction.java +++ b/core/java/android/service/controls/actions/FloatAction.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/actions/ModeAction.aidl b/core/java/android/service/controls/actions/ModeAction.aidl deleted file mode 100644 index 3ef89e0bfce9c..0000000000000 --- a/core/java/android/service/controls/actions/ModeAction.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/ModeAction.java b/core/java/android/service/controls/actions/ModeAction.java index 0bd1d24e873f2..ca40974d929bc 100644 --- a/core/java/android/service/controls/actions/ModeAction.java +++ b/core/java/android/service/controls/actions/ModeAction.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/actions/MultiFloatAction.aidl b/core/java/android/service/controls/actions/MultiFloatAction.aidl deleted file mode 100644 index bcba75810b0cf..0000000000000 --- a/core/java/android/service/controls/actions/MultiFloatAction.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/actions/MultiFloatAction.java b/core/java/android/service/controls/actions/MultiFloatAction.java index aef8a785a6656..e5740795ab900 100644 --- a/core/java/android/service/controls/actions/MultiFloatAction.java +++ b/core/java/android/service/controls/actions/MultiFloatAction.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/ControlButton.java b/core/java/android/service/controls/templates/ControlButton.java index e03ac6f8bf517..157e231b1da69 100644 --- a/core/java/android/service/controls/templates/ControlButton.java +++ b/core/java/android/service/controls/templates/ControlButton.java @@ -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 CREATOR = new Creator() { + public static final @NonNull Creator CREATOR = new Creator() { @Override public ControlButton createFromParcel(Parcel source) { return new ControlButton(source); diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index bf194f8efcdaf..d2c0f76907e67 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -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 CREATOR = new Creator() { - @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; } } } diff --git a/core/java/android/service/controls/templates/StatelessTemplate.aidl b/core/java/android/service/controls/templates/ControlTemplateWrapper.aidl similarity index 87% rename from core/java/android/service/controls/templates/StatelessTemplate.aidl rename to core/java/android/service/controls/templates/ControlTemplateWrapper.aidl index 02e18d9ac83d5..208ca4e1c5a14 100644 --- a/core/java/android/service/controls/templates/StatelessTemplate.aidl +++ b/core/java/android/service/controls/templates/ControlTemplateWrapper.aidl @@ -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; \ No newline at end of file +parcelable ControlTemplateWrapper; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/ControlTemplateWrapper.java b/core/java/android/service/controls/templates/ControlTemplateWrapper.java new file mode 100644 index 0000000000000..7957260475eec --- /dev/null +++ b/core/java/android/service/controls/templates/ControlTemplateWrapper.java @@ -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 CREATOR = + new Creator() { + @Override + public ControlTemplateWrapper createFromParcel(@NonNull Parcel source) { + return new ControlTemplateWrapper( + ControlTemplate.createTemplateFromBundle(source.readBundle())); + } + + @Override + public ControlTemplateWrapper[] newArray(int size) { + return new ControlTemplateWrapper[size]; + } + }; +} diff --git a/core/java/android/service/controls/templates/CoordinatedRangeTemplate.aidl b/core/java/android/service/controls/templates/CoordinatedRangeTemplate.aidl deleted file mode 100644 index 972142c5aa037..0000000000000 --- a/core/java/android/service/controls/templates/CoordinatedRangeTemplate.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/CoordinatedRangeTemplate.java b/core/java/android/service/controls/templates/CoordinatedRangeTemplate.java index 3d820c49eeab8..6aa5480f0611c 100644 --- a/core/java/android/service/controls/templates/CoordinatedRangeTemplate.java +++ b/core/java/android/service/controls/templates/CoordinatedRangeTemplate.java @@ -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 CREATOR = - new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/DiscreteToggleTemplate.aidl b/core/java/android/service/controls/templates/DiscreteToggleTemplate.aidl deleted file mode 100644 index d22e375016051..0000000000000 --- a/core/java/android/service/controls/templates/DiscreteToggleTemplate.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/DiscreteToggleTemplate.java b/core/java/android/service/controls/templates/DiscreteToggleTemplate.java index a8c193c5b1dce..7a1331a35ed45 100644 --- a/core/java/android/service/controls/templates/DiscreteToggleTemplate.java +++ b/core/java/android/service/controls/templates/DiscreteToggleTemplate.java @@ -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 Negative input - * @param positiveButton a {@ControlButton} for the Positive input + * @param negativeButton a {@link ControlButton} for the Negative input + * @param positiveButton a {@link ControlButton} for the Positive 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 CREATOR = - new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/RangeTemplate.java b/core/java/android/service/controls/templates/RangeTemplate.java index bb79d83b18256..fe0d16707c9dd 100644 --- a/core/java/android/service/controls/templates/RangeTemplate.java +++ b/core/java/android/service/controls/templates/RangeTemplate.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/StatelessTemplate.java b/core/java/android/service/controls/templates/StatelessTemplate.java index 12ab9bc567acd..3f98beac9cf89 100644 --- a/core/java/android/service/controls/templates/StatelessTemplate.java +++ b/core/java/android/service/controls/templates/StatelessTemplate.java @@ -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 CREATOR = new Creator() { - @Override - public StatelessTemplate createFromParcel(Parcel source) { - return new StatelessTemplate(source.readBundle()); - } - - @Override - public StatelessTemplate[] newArray(int size) { - return new StatelessTemplate[size]; - } - }; } diff --git a/core/java/android/service/controls/templates/TemperatureControlTemplate.aidl b/core/java/android/service/controls/templates/TemperatureControlTemplate.aidl deleted file mode 100644 index 7994d26516105..0000000000000 --- a/core/java/android/service/controls/templates/TemperatureControlTemplate.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/TemperatureControlTemplate.java b/core/java/android/service/controls/templates/TemperatureControlTemplate.java index 987621e011c2d..9d8dca6278c5a 100644 --- a/core/java/android/service/controls/templates/TemperatureControlTemplate.java +++ b/core/java/android/service/controls/templates/TemperatureControlTemplate.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/ThumbnailTemplate.aidl b/core/java/android/service/controls/templates/ThumbnailTemplate.aidl deleted file mode 100644 index 81c879b227bb3..0000000000000 --- a/core/java/android/service/controls/templates/ThumbnailTemplate.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/ThumbnailTemplate.java b/core/java/android/service/controls/templates/ThumbnailTemplate.java index 111d60dc80c92..72179f4edc5e6 100644 --- a/core/java/android/service/controls/templates/ThumbnailTemplate.java +++ b/core/java/android/service/controls/templates/ThumbnailTemplate.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/ToggleRangeTemplate.aidl b/core/java/android/service/controls/templates/ToggleRangeTemplate.aidl deleted file mode 100644 index 2611284883653..0000000000000 --- a/core/java/android/service/controls/templates/ToggleRangeTemplate.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/service/controls/templates/ToggleRangeTemplate.java b/core/java/android/service/controls/templates/ToggleRangeTemplate.java index aa6f6fbad97bc..af43b94699d21 100644 --- a/core/java/android/service/controls/templates/ToggleRangeTemplate.java +++ b/core/java/android/service/controls/templates/ToggleRangeTemplate.java @@ -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 CREATOR = new Creator() { - - @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]; - } - }; } diff --git a/core/java/android/service/controls/templates/ToggleTemplate.java b/core/java/android/service/controls/templates/ToggleTemplate.java index 0e5fd336e2a73..e4aa6b0d6cec1 100644 --- a/core/java/android/service/controls/templates/ToggleTemplate.java +++ b/core/java/android/service/controls/templates/ToggleTemplate.java @@ -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 CREATOR = new Creator() { - @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]; - } - }; - } diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java new file mode 100644 index 0000000000000..2648a0644dc13 --- /dev/null +++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java @@ -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> captor = ArgumentCaptor.forClass(List.class); + + ArrayList 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 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> captor = ArgumentCaptor.forClass(List.class); + + ArrayList list = new ArrayList<>(); + list.add(control); + + mControlsProviderService.setControls(list); + mControlsProvider.load(mLoadCallback); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mLoadCallback).accept(eq(mToken), captor.capture()); + List 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 controlCaptor = + ArgumentCaptor.forClass(Control.class); + ArgumentCaptor subscriptionCaptor = + ArgumentCaptor.forClass(IControlsSubscription.Stub.class); + + ArrayList list = new ArrayList<>(); + list.add(control); + + mControlsProviderService.setControls(list); + + mControlsProvider.subscribe(new ArrayList(), 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 mControls; + + public void setControls(List controls) { + mControls = controls; + } + + @Override + public void loadAvailableControls(Consumer> cb) { + cb.accept(mControls); + } + + @Override + public Publisher publisherFor(List ids) { + return new Publisher() { + 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 cb) { + cb.accept(ControlAction.RESPONSE_OK); + } + } +} + + diff --git a/core/tests/coretests/src/android/service/controls/ControlActionTest.java b/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java similarity index 84% rename from core/tests/coretests/src/android/service/controls/ControlActionTest.java rename to core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java index d0264da206d9b..10a7b76390ec4 100644 --- a/core/tests/coretests/src/android/service/controls/ControlActionTest.java +++ b/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java @@ -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(); } } diff --git a/core/tests/coretests/src/android/service/controls/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java similarity index 71% rename from core/tests/coretests/src/android/service/controls/ControlTemplateTest.java rename to core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java index 2756891c7c366..c9b5eecdcd212 100644 --- a/core/tests/coretests/src/android/service/controls/ControlTemplateTest.java +++ b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java @@ -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(); } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 80e48b925fc92..2db2cf1af191e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -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 = ArrayMap() - private val serviceCallback = object : IControlsProviderCallback.Stub() { - override fun onLoad(token: IBinder, controls: MutableList) { + private val loadCallbackService = object : IControlsLoadCallback.Stub() { + override fun accept(token: IBinder, controls: MutableList) { backgroundExecutor.execute(OnLoadRunnable(token, controls)) } + } - override fun onRefreshState(token: IBinder, controlStates: List) { - 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 + 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( } } } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 4d958224e9171..e098faa00d038 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -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) + fun refreshStatus(componentName: ComponentName, control: Control) fun onActionResponse( componentName: ComponentName, controlId: String, @ControlAction.ResponseResult response: Int ) fun clearFavorites() -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 7e328e4671299..d5b5b5f0442eb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -210,20 +210,20 @@ class ControlsControllerImpl @Inject constructor ( } } - override fun refreshStatus(componentName: ComponentName, controls: List) { + 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 ( } } } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 79057ad25b206..99aa3601ba301 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -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) -> 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() private var unbindImmediate = false private var requiresBound = false private var isBound = false @GuardedBy("queuedMessages") private val queuedMessages: MutableSet = 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 } } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt similarity index 52% rename from packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt rename to packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt index 882a10d544319..5c812b1347e54 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt @@ -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): Boolean { + fun subscribe(controlIds: List, 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() } } -} \ No newline at end of file + + fun action( + controlId: String, + action: ControlAction, + cb: IControlsActionCallback + ): Boolean { + return callThroughService { + service.action(controlId, ControlActionWrapper(action), cb) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 937216230123b..3949c5929a85e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -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") diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index a19c299940cd8..be86a9c15e5f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -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()) } -} \ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index 556bb4092d076..4fc1cca76be65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -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 + private val componentName = ComponentName("test.pkg", "test.cls") private lateinit var manager: ControlsProviderLifecycleManager private lateinit var executor: DelayableExecutor + companion object { + fun capture(argumentCaptor: ArgumentCaptor): 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) -> 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()) } -} \ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt deleted file mode 100644 index d6993c06a3c36..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt new file mode 100644 index 0000000000000..9e7ce06bb74fd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt @@ -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 + + private val exception = RemoteException() + + private lateinit var wrapper: ServiceWrapper + + companion object { + fun capture(argumentCaptor: ArgumentCaptor): 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) + } +}