blob: d01bc2524332bb71f4af7de08ce58f3001a08ee2 [file] [log] [blame]
/*
* 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;
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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Represents a physical object that can be represented by a {@link ControlTemplate} and whose
* properties may be modified through a {@link ControlAction}.
*
* The information is provided by a {@link ControlsProviderService} and represents static
* information (not current status) about the device.
* <p>
* Each control needs a unique (per provider) identifier that is persistent across reboots of the
* system.
* <p>
* Each {@link Control} will have a name, a subtitle and will optionally belong to a structure
* and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService}
* and will be used to display the control as well as group them for management.
* <p>
* Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors
* used to display it.
* <p>
* An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
* allows for further actions should be provided.
*/
public final class Control implements Parcelable {
private static final String TAG = "Control";
private static final int NUM_STATUS = 5;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATUS_UNKNOWN,
STATUS_OK,
STATUS_NOT_FOUND,
STATUS_ERROR,
STATUS_DISABLED,
})
public @interface Status {};
public static final int STATUS_UNKNOWN = 0;
/**
* The device corresponding to the {@link Control} is responding correctly.
*/
public static final int STATUS_OK = 1;
/**
* The device corresponding to the {@link Control} cannot be found or was removed.
*/
public static final int STATUS_NOT_FOUND = 2;
/**
* The device corresponding to the {@link Control} is in an error state.
*/
public static final int STATUS_ERROR = 3;
/**
* The {@link Control} is currently disabled.
*/
public static final int STATUS_DISABLED = 4;
private final @NonNull String mControlId;
private final @DeviceTypes.DeviceType int mDeviceType;
private final @NonNull CharSequence mTitle;
private final @NonNull CharSequence mSubtitle;
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;
/**
* @param controlId the unique persistent identifier for this object.
* @param deviceType the type of device for this control. This will determine icons and colors.
* @param title the user facing name of this control (e.g. "Bedroom thermostat").
* @param subtitle a user facing subtitle with extra information about this control
* @param structure a user facing name for the structure containing the device associated with
* this control.
* @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
*/
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) {
Preconditions.checkNotNull(controlId);
Preconditions.checkNotNull(title);
Preconditions.checkNotNull(subtitle);
Preconditions.checkNotNull(appIntent);
Preconditions.checkNotNull(controlTemplate);
Preconditions.checkNotNull(statusText);
mControlId = controlId;
if (!DeviceTypes.validDeviceType(deviceType)) {
Log.e(TAG, "Invalid device type:" + deviceType);
mDeviceType = DeviceTypes.TYPE_UNKNOWN;
} else {
mDeviceType = deviceType;
}
mTitle = title;
mSubtitle = subtitle;
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);
} else {
mStatus = status;
}
mControlTemplate = controlTemplate;
mStatusText = statusText;
}
/**
* @param in
* @hide
*/
Control(Parcel in) {
mControlId = in.readString();
mDeviceType = in.readInt();
mTitle = in.readCharSequence();
mSubtitle = in.readCharSequence();
if (in.readByte() == (byte) 1) {
mStructure = in.readCharSequence();
} else {
mStructure = null;
}
if (in.readByte() == (byte) 1) {
mZone = in.readCharSequence();
} else {
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();
ControlTemplateWrapper wrapper = ControlTemplateWrapper.CREATOR.createFromParcel(in);
mControlTemplate = wrapper.getWrappedTemplate();
mStatusText = in.readCharSequence();
}
/**
* @return the identifier for the {@link Control}
*/
@NonNull
public String getControlId() {
return mControlId;
}
/**
* @return type of device represented by this {@link Control}, used to determine the default
* icon and color
*/
@DeviceTypes.DeviceType
public int getDeviceType() {
return mDeviceType;
}
/**
* @return the user facing name of the {@link Control}
*/
@NonNull
public CharSequence getTitle() {
return mTitle;
}
/**
* @return additional information about the {@link Control}, to appear underneath the title
*/
@NonNull
public CharSequence getSubtitle() {
return mSubtitle;
}
/**
* Optional top-level group to help define the {@link Control}'s location, visible to the user.
* If not present, the application name will be used as the top-level group. A structure
* contains zones which contains controls.
*
* @return name of the structure containing the control
*/
@Nullable
public CharSequence getStructure() {
return mStructure;
}
/**
* Optional group name to help define the {@link Control}'s location within a structure,
* visible to the user. A structure contains zones which contains controls.
*
* @return name of the zone containing the control
*/
@Nullable
public CharSequence getZone() {
return mZone;
}
/**
* @return a {@link PendingIntent} linking to an Activity for the {@link Control}
*/
@NonNull
public PendingIntent getAppIntent() {
return mAppIntent;
}
/**
* Optional icon to be shown with the {@link Control}. It is highly recommended
* to let the system default the icon unless the default icon is not suitable.
*
* @return icon to show
*/
@Nullable
public Icon getCustomIcon() {
return mCustomIcon;
}
/**
* Optional color to be shown with the {@link Control}. It is highly recommended
* to let the system default the color unless the default is not suitable for the
* application.
*
* @return background color to use
*/
@Nullable
public ColorStateList getCustomColor() {
return mCustomColor;
}
/**
* @return status of the {@link Control}, used to convey information about the attempt to
* fetch the current state
*/
@Status
public int getStatus() {
return mStatus;
}
/**
* @return instance of {@link ControlTemplate}, that defines how the {@link Control} will
* behave and what interactions are available to the user
*/
@NonNull
public ControlTemplate getControlTemplate() {
return mControlTemplate;
}
/**
* @return user-facing text description of the {@link Control}'s status, describing its current
* state
*/
@NonNull
public CharSequence getStatusText() {
return mStatusText;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mControlId);
dest.writeInt(mDeviceType);
dest.writeCharSequence(mTitle);
dest.writeCharSequence(mSubtitle);
if (mStructure != null) {
dest.writeByte((byte) 1);
dest.writeCharSequence(mStructure);
} else {
dest.writeByte((byte) 0);
}
if (mZone != null) {
dest.writeByte((byte) 1);
dest.writeCharSequence(mZone);
} else {
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);
new ControlTemplateWrapper(mControlTemplate).writeToParcel(dest, flags);
dest.writeCharSequence(mStatusText);
}
public static final @NonNull Creator<Control> CREATOR = new Creator<Control>() {
@Override
public Control createFromParcel(@NonNull Parcel source) {
return new Control(source);
}
@Override
public Control[] newArray(int size) {
return new Control[size];
}
};
/**
* Builder class for {@link Control}.
*
* This class facilitates the creation of {@link Control} with no state. Must be used to
* provide controls for {@link ControlsProviderService#createPublisherForAllAvailable} and
* {@link ControlsProviderService#createPublisherForSuggested}.
*
* It provides the following defaults for non-optional parameters:
* <ul>
* <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
* <li> Title: {@code ""}
* <li> Subtitle: {@code ""}
* </ul>
* This fixes the values relating to state of the {@link Control} as required by
* {@link ControlsProviderService#createPublisherForAllAvailable}:
* <ul>
* <li> Status: {@link Status#STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#getNoTemplateObject}
* <li> Status text: {@code ""}
* </ul>
*/
@SuppressLint("MutableBareField")
public static final class StatelessBuilder {
private static final String TAG = "StatelessBuilder";
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}
* @param appIntent the pending intent linking to the device Activity
*/
public StatelessBuilder(@NonNull String controlId,
@NonNull PendingIntent 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 StatelessBuilder(@NonNull Control 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;
}
/**
* @param controlId the identifier for the {@link Control}
* @return {@code this}
*/
@NonNull
public StatelessBuilder setControlId(@NonNull String controlId) {
Preconditions.checkNotNull(controlId);
mControlId = controlId;
return this;
}
/**
* @param deviceType type of device represented by this {@link Control}, used to
* determine the default icon and color
* @return {@code this}
*/
@NonNull
public StatelessBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
if (!DeviceTypes.validDeviceType(deviceType)) {
Log.e(TAG, "Invalid device type:" + deviceType);
mDeviceType = DeviceTypes.TYPE_UNKNOWN;
} else {
mDeviceType = deviceType;
}
return this;
}
/**
* @param title the user facing name of the {@link Control}
* @return {@code this}
*/
@NonNull
public StatelessBuilder setTitle(@NonNull CharSequence title) {
Preconditions.checkNotNull(title);
mTitle = title;
return this;
}
/**
* @param subtitle additional information about the {@link Control}, to appear underneath
* the title
* @return {@code this}
*/
@NonNull
public StatelessBuilder setSubtitle(@NonNull CharSequence subtitle) {
Preconditions.checkNotNull(subtitle);
mSubtitle = subtitle;
return this;
}
/**
* Optional top-level group to help define the {@link Control}'s location, visible to the
* user. If not present, the application name will be used as the top-level group. A
* structure contains zones which contains controls.
*
* @param structure name of the structure containing the control
* @return {@code this}
*/
@NonNull
public StatelessBuilder setStructure(@Nullable CharSequence structure) {
mStructure = structure;
return this;
}
/**
* Optional group name to help define the {@link Control}'s location within a structure,
* visible to the user. A structure contains zones which contains controls.
*
* @param zone name of the zone containing the control
* @return {@code this}
*/
@NonNull
public StatelessBuilder setZone(@Nullable CharSequence zone) {
mZone = zone;
return this;
}
/**
* @param appIntent a {@link PendingIntent} linking to an Activity for the {@link Control}
* @return {@code this}
*/
@NonNull
public StatelessBuilder setAppIntent(@NonNull PendingIntent appIntent) {
Preconditions.checkNotNull(appIntent);
mAppIntent = appIntent;
return this;
}
/**
* Optional icon to be shown with the {@link Control}. It is highly recommended
* to let the system default the icon unless the default icon is not suitable.
*
* @param customIcon icon to show
* @return {@code this}
*/
@NonNull
public StatelessBuilder setCustomIcon(@Nullable Icon customIcon) {
mCustomIcon = customIcon;
return this;
}
/**
* Optional color to be shown with the {@link Control}. It is highly recommended
* to let the system default the color unless the default is not suitable for the
* application.
*
* @param customColor background color to use
* @return {@code this}
*/
@NonNull
public StatelessBuilder setCustomColor(@Nullable ColorStateList customColor) {
mCustomColor = customColor;
return this;
}
/**
* @return a valid {@link Control}
*/
@NonNull
public Control build() {
return new Control(mControlId,
mDeviceType,
mTitle,
mSubtitle,
mStructure,
mZone,
mAppIntent,
mCustomIcon,
mCustomColor,
STATUS_UNKNOWN,
ControlTemplate.NO_TEMPLATE,
"");
}
}
/**
* Builder class for {@link Control} that contains state information.
*
* State information is passed through an instance of a {@link ControlTemplate} and will
* determine how the user can interact with the {@link Control}. User interactions will
* be sent through the method call {@link ControlsProviderService#performControlAction}
* with an instance of {@link ControlAction} to convey any potential new value.
*
* Must be used to provide controls for {@link ControlsProviderService#createPublisherFor}.
*
* It provides the following defaults for non-optional parameters:
* <ul>
* <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
* <li> Title: {@code ""}
* <li> Subtitle: {@code ""}
* <li> Status: {@link Status#STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#getNoTemplateObject}
* <li> Status text: {@code ""}
* </ul>
*/
public static final class StatefulBuilder {
private static final String TAG = "StatefulBuilder";
private @NonNull String mControlId;
private @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
private @NonNull CharSequence mTitle = "";
private @NonNull CharSequence mSubtitle = "";
private @Nullable CharSequence mStructure;
private @Nullable CharSequence mZone;
private @NonNull PendingIntent mAppIntent;
private @Nullable Icon mCustomIcon;
private @Nullable ColorStateList mCustomColor;
private @Status int mStatus = STATUS_UNKNOWN;
private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
private @NonNull CharSequence mStatusText = "";
/**
* @param controlId the identifier for the {@link Control}.
* @param appIntent the pending intent linking to the device Activity.
*/
public StatefulBuilder(@NonNull String controlId,
@NonNull PendingIntent 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) {
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;
}
/**
* @param controlId the identifier for the {@link Control}.
* @return {@code this}
*/
@NonNull
public StatefulBuilder setControlId(@NonNull String controlId) {
Preconditions.checkNotNull(controlId);
mControlId = controlId;
return this;
}
/**
* @param deviceType type of device represented by this {@link Control}, used to
* determine the default icon and color
* @return {@code this}
*/
@NonNull
public StatefulBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
if (!DeviceTypes.validDeviceType(deviceType)) {
Log.e(TAG, "Invalid device type:" + deviceType);
mDeviceType = DeviceTypes.TYPE_UNKNOWN;
} else {
mDeviceType = deviceType;
}
return this;
}
/**
* @param title the user facing name of the {@link Control}
* @return {@code this}
*/
@NonNull
public StatefulBuilder setTitle(@NonNull CharSequence title) {
Preconditions.checkNotNull(title);
mTitle = title;
return this;
}
/**
* @param subtitle additional information about the {@link Control}, to appear underneath
* the title
* @return {@code this}
*/
@NonNull
public StatefulBuilder setSubtitle(@NonNull CharSequence subtitle) {
Preconditions.checkNotNull(subtitle);
mSubtitle = subtitle;
return this;
}
/**
* Optional top-level group to help define the {@link Control}'s location, visible to the
* user. If not present, the application name will be used as the top-level group. A
* structure contains zones which contains controls.
*
* @param structure name of the structure containing the control
* @return {@code this}
*/
@NonNull
public StatefulBuilder setStructure(@Nullable CharSequence structure) {
mStructure = structure;
return this;
}
/**
* Optional group name to help define the {@link Control}'s location within a structure,
* visible to the user. A structure contains zones which contains controls.
*
* @param zone name of the zone containing the control
* @return {@code this}
*/
@NonNull
public StatefulBuilder setZone(@Nullable CharSequence zone) {
mZone = zone;
return this;
}
/**
* @param appIntent a {@link PendingIntent} linking to an Activity for the {@link Control}
* @return {@code this}
*/
@NonNull
public StatefulBuilder setAppIntent(@NonNull PendingIntent appIntent) {
Preconditions.checkNotNull(appIntent);
mAppIntent = appIntent;
return this;
}
/**
* Optional icon to be shown with the {@link Control}. It is highly recommended
* to let the system default the icon unless the default icon is not suitable.
*
* @param customIcon icon to show
* @return {@code this}
*/
@NonNull
public StatefulBuilder setCustomIcon(@Nullable Icon customIcon) {
mCustomIcon = customIcon;
return this;
}
/**
* Optional color to be shown with the {@link Control}. It is highly recommended
* to let the system default the color unless the default is not suitable for the
* application.
*
* @param customColor background color to use
* @return {@code this}
*/
@NonNull
public StatefulBuilder setCustomColor(@Nullable ColorStateList customColor) {
mCustomColor = customColor;
return this;
}
/**
* @param status status of the {@link Control}, used to convey information about the
* attempt to fetch the current state
* @return {@code this}
*/
@NonNull
public StatefulBuilder setStatus(@Status int status) {
if (status < 0 || status >= NUM_STATUS) {
mStatus = STATUS_UNKNOWN;
Log.e(TAG, "Status unknown:" + status);
} else {
mStatus = status;
}
return this;
}
/**
* @param controlTemplate instance of {@link ControlTemplate}, that defines how the
* {@link Control} will behave and what interactions are
* available to the user
* @return {@code this}
*/
@NonNull
public StatefulBuilder setControlTemplate(@NonNull ControlTemplate controlTemplate) {
Preconditions.checkNotNull(controlTemplate);
mControlTemplate = controlTemplate;
return this;
}
/**
* @param statusText user-facing text description of the {@link Control}'s status,
* describing its current state
* @return {@code this}
*/
@NonNull
public StatefulBuilder setStatusText(@NonNull CharSequence statusText) {
Preconditions.checkNotNull(statusText);
mStatusText = statusText;
return this;
}
/**
* @return a valid {@link Control}
*/
@NonNull
public Control build() {
return new Control(mControlId,
mDeviceType,
mTitle,
mSubtitle,
mStructure,
mZone,
mAppIntent,
mCustomIcon,
mCustomColor,
mStatus,
mControlTemplate,
mStatusText);
}
}
}