1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.controls;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SuppressLint;
23 import android.app.PendingIntent;
24 import android.content.Intent;
25 import android.content.res.ColorStateList;
26 import android.graphics.drawable.Icon;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.service.controls.actions.ControlAction;
30 import android.service.controls.templates.ControlTemplate;
31 import android.service.controls.templates.ControlTemplateWrapper;
32 import android.util.Log;
33 
34 import com.android.internal.util.Preconditions;
35 
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 
39 /**
40  * Represents a physical object that can be represented by a {@link ControlTemplate} and whose
41  * properties may be modified through a {@link ControlAction}.
42  *
43  * The information is provided by a {@link ControlsProviderService} and represents static
44  * information (not current status) about the device.
45  * <p>
46  * Each control needs a unique (per provider) identifier that is persistent across reboots of the
47  * system.
48  * <p>
49  * Each {@link Control} will have a name, a subtitle and will optionally belong to a structure
50  * and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService}
51  * and will be used to display the control as well as group them for management.
52  * <p>
53  * Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors
54  * used to display it.
55  * <p>
56  * An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
57  * allows for further actions should be provided.
58  */
59 public final class Control implements Parcelable {
60     private static final String TAG = "Control";
61 
62     private static final int NUM_STATUS = 5;
63     /**
64      * @hide
65      */
66     @Retention(RetentionPolicy.SOURCE)
67     @IntDef({
68             STATUS_UNKNOWN,
69             STATUS_OK,
70             STATUS_NOT_FOUND,
71             STATUS_ERROR,
72             STATUS_DISABLED,
73     })
74     public @interface Status {};
75 
76     /**
77      * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is
78      * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes
79      * to indicate the proper device state.
80      */
81     public static final int STATUS_UNKNOWN = 0;
82 
83     /**
84      * Used to indicate that the state of the device was successfully retrieved. This includes
85      * all scenarios where the device may have a warning for the user, such as "Lock jammed",
86      * or "Vacuum stuck". Any information for the user should be set through
87      * {@link StatefulBuilder#setStatusText}.
88      */
89     public static final int STATUS_OK = 1;
90 
91     /**
92      * The device corresponding to the {@link Control} cannot be found or was removed. The user
93      * will be alerted and directed to the application to resolve.
94      */
95     public static final int STATUS_NOT_FOUND = 2;
96 
97     /**
98      * Used to indicate that there was a temporary error while loading the device state. A default
99      * error message will be displayed in place of any custom text that was set through
100      * {@link StatefulBuilder#setStatusText}.
101      */
102     public static final int STATUS_ERROR = 3;
103 
104     /**
105      * The {@link Control} is currently disabled.  A default error message will be displayed in
106      * place of any custom text that was set through {@link StatefulBuilder#setStatusText}.
107      */
108     public static final int STATUS_DISABLED = 4;
109 
110     private final @NonNull String mControlId;
111     private final @DeviceTypes.DeviceType int mDeviceType;
112     private final @NonNull CharSequence mTitle;
113     private final @NonNull CharSequence mSubtitle;
114     private final @Nullable CharSequence mStructure;
115     private final @Nullable CharSequence mZone;
116     private final @NonNull PendingIntent mAppIntent;
117 
118     private final @Nullable Icon mCustomIcon;
119     private final @Nullable ColorStateList mCustomColor;
120 
121     private final @Status int mStatus;
122     private final @NonNull ControlTemplate mControlTemplate;
123     private final @NonNull CharSequence mStatusText;
124     private final boolean mAuthRequired;
125 
126     /**
127      * @param controlId the unique persistent identifier for this object.
128      * @param deviceType the type of device for this control. This will determine icons and colors.
129      * @param title the user facing name of this control (e.g. "Bedroom thermostat").
130      * @param subtitle a user facing subtitle with extra information about this control
131      * @param structure a user facing name for the structure containing the device associated with
132      *                  this control.
133      * @param zone
134      * @param appIntent a {@link PendingIntent} linking to a page to interact with the
135      *                  corresponding device.
136      * @param customIcon
137      * @param customColor
138      * @param status
139      * @param controlTemplate
140      * @param statusText
141      * @param authRequired true if the control can not be interacted with until the device is
142      *                     unlocked
143      */
Control(@onNull 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, boolean authRequired)144     Control(@NonNull String controlId,
145             @DeviceTypes.DeviceType int deviceType,
146             @NonNull CharSequence title,
147             @NonNull CharSequence subtitle,
148             @Nullable CharSequence structure,
149             @Nullable CharSequence zone,
150             @NonNull PendingIntent appIntent,
151             @Nullable Icon customIcon,
152             @Nullable ColorStateList customColor,
153             @Status int status,
154             @NonNull ControlTemplate controlTemplate,
155             @NonNull CharSequence statusText,
156             boolean authRequired) {
157         Preconditions.checkNotNull(controlId);
158         Preconditions.checkNotNull(title);
159         Preconditions.checkNotNull(subtitle);
160         Preconditions.checkNotNull(appIntent);
161         Preconditions.checkNotNull(controlTemplate);
162         Preconditions.checkNotNull(statusText);
163         mControlId = controlId;
164         if (!DeviceTypes.validDeviceType(deviceType)) {
165             Log.e(TAG, "Invalid device type:" + deviceType);
166             mDeviceType = DeviceTypes.TYPE_UNKNOWN;
167         } else {
168             mDeviceType = deviceType;
169         }
170         mTitle = title;
171         mSubtitle = subtitle;
172         mStructure = structure;
173         mZone = zone;
174         mAppIntent = appIntent;
175 
176         mCustomColor = customColor;
177         mCustomIcon = customIcon;
178 
179         if (status < 0 || status >= NUM_STATUS) {
180             mStatus = STATUS_UNKNOWN;
181             Log.e(TAG, "Status unknown:" + status);
182         } else {
183             mStatus = status;
184         }
185         mControlTemplate = controlTemplate;
186         mStatusText = statusText;
187         mAuthRequired = authRequired;
188     }
189 
190     /**
191      * @param in
192      * @hide
193      */
Control(Parcel in)194     Control(Parcel in) {
195         mControlId = in.readString();
196         mDeviceType = in.readInt();
197         mTitle = in.readCharSequence();
198         mSubtitle = in.readCharSequence();
199         if (in.readByte() == (byte) 1) {
200             mStructure = in.readCharSequence();
201         } else {
202             mStructure = null;
203         }
204         if (in.readByte() == (byte) 1) {
205             mZone = in.readCharSequence();
206         } else {
207             mZone = null;
208         }
209         mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
210 
211         if (in.readByte() == (byte) 1) {
212             mCustomIcon = Icon.CREATOR.createFromParcel(in);
213         } else {
214             mCustomIcon = null;
215         }
216 
217         if (in.readByte() == (byte) 1) {
218             mCustomColor = ColorStateList.CREATOR.createFromParcel(in);
219         } else {
220             mCustomColor = null;
221         }
222 
223         mStatus = in.readInt();
224         ControlTemplateWrapper wrapper = ControlTemplateWrapper.CREATOR.createFromParcel(in);
225         mControlTemplate = wrapper.getWrappedTemplate();
226         mStatusText = in.readCharSequence();
227         mAuthRequired = in.readBoolean();
228     }
229 
230     /**
231      * @return the identifier for the {@link Control}
232      */
233     @NonNull
getControlId()234     public String getControlId() {
235         return mControlId;
236     }
237 
238 
239     /**
240      * @return type of device represented by this {@link Control}, used to determine the default
241      *         icon and color
242      */
243     @DeviceTypes.DeviceType
getDeviceType()244     public int getDeviceType() {
245         return mDeviceType;
246     }
247 
248     /**
249      * @return the user facing name of the {@link Control}
250      */
251     @NonNull
getTitle()252     public CharSequence getTitle() {
253         return mTitle;
254     }
255 
256     /**
257      * @return additional information about the {@link Control}, to appear underneath the title
258      */
259     @NonNull
getSubtitle()260     public CharSequence getSubtitle() {
261         return mSubtitle;
262     }
263 
264     /**
265      * Optional top-level group to help define the {@link Control}'s location, visible to the user.
266      * If not present, the application name will be used as the top-level group. A structure
267      * contains zones which contains controls.
268      *
269      * @return name of the structure containing the control
270      */
271     @Nullable
getStructure()272     public CharSequence getStructure() {
273         return mStructure;
274     }
275 
276     /**
277      * Optional group name to help define the {@link Control}'s location within a structure,
278      * visible to the user. A structure contains zones which contains controls.
279      *
280      * @return name of the zone containing the control
281      */
282     @Nullable
getZone()283     public CharSequence getZone() {
284         return mZone;
285     }
286 
287     /**
288      * @return a {@link PendingIntent} linking to an Activity for the {@link Control}
289      */
290     @NonNull
getAppIntent()291     public PendingIntent getAppIntent() {
292         return mAppIntent;
293     }
294 
295     /**
296      * Optional icon to be shown with the {@link Control}. It is highly recommended
297      * to let the system default the icon unless the default icon is not suitable.
298      *
299      * @return icon to show
300      */
301     @Nullable
getCustomIcon()302     public Icon getCustomIcon() {
303         return mCustomIcon;
304     }
305 
306     /**
307      * Optional color to be shown with the {@link Control}. It is highly recommended
308      * to let the system default the color unless the default is not suitable for the
309      * application.
310      *
311      * @return background color to use
312      */
313     @Nullable
getCustomColor()314     public ColorStateList getCustomColor() {
315         return mCustomColor;
316     }
317 
318     /**
319      * @return status of the {@link Control}, used to convey information about the attempt to
320      *         fetch the current state
321      */
322     @Status
getStatus()323     public int getStatus() {
324         return mStatus;
325     }
326 
327     /**
328      * @return instance of {@link ControlTemplate}, that defines how the {@link Control} will
329      *         behave and what interactions are available to the user
330      */
331     @NonNull
getControlTemplate()332     public ControlTemplate getControlTemplate() {
333         return mControlTemplate;
334     }
335 
336     /**
337      * @return user-facing text description of the {@link Control}'s status, describing its current
338      *         state
339      */
340     @NonNull
getStatusText()341     public CharSequence getStatusText() {
342         return mStatusText;
343     }
344 
345     /**
346      * @return true if the control can not be interacted with until the device is unlocked
347      */
isAuthRequired()348     public boolean isAuthRequired() {
349         return mAuthRequired;
350     }
351 
352     @Override
describeContents()353     public int describeContents() {
354         return 0;
355     }
356 
357     @Override
writeToParcel(@onNull Parcel dest, int flags)358     public void writeToParcel(@NonNull Parcel dest, int flags) {
359         dest.writeString(mControlId);
360         dest.writeInt(mDeviceType);
361         dest.writeCharSequence(mTitle);
362         dest.writeCharSequence(mSubtitle);
363         if (mStructure != null) {
364             dest.writeByte((byte) 1);
365             dest.writeCharSequence(mStructure);
366         } else {
367             dest.writeByte((byte) 0);
368         }
369         if (mZone != null) {
370             dest.writeByte((byte) 1);
371             dest.writeCharSequence(mZone);
372         } else {
373             dest.writeByte((byte) 0);
374         }
375         mAppIntent.writeToParcel(dest, flags);
376         if (mCustomIcon != null) {
377             dest.writeByte((byte) 1);
378             mCustomIcon.writeToParcel(dest, flags);
379         } else {
380             dest.writeByte((byte) 0);
381         }
382         if (mCustomColor != null) {
383             dest.writeByte((byte) 1);
384             mCustomColor.writeToParcel(dest, flags);
385         } else {
386             dest.writeByte((byte) 0);
387         }
388 
389         dest.writeInt(mStatus);
390         new ControlTemplateWrapper(mControlTemplate).writeToParcel(dest, flags);
391         dest.writeCharSequence(mStatusText);
392         dest.writeBoolean(mAuthRequired);
393     }
394 
395     public static final @NonNull Creator<Control> CREATOR = new Creator<Control>() {
396         @Override
397         public Control createFromParcel(@NonNull Parcel source) {
398             return new Control(source);
399         }
400 
401         @Override
402         public Control[] newArray(int size) {
403             return new Control[size];
404         }
405     };
406 
407     /**
408      * Builder class for {@link Control}.
409      *
410      * This class facilitates the creation of {@link Control} with no state. Must be used to
411      * provide controls for {@link ControlsProviderService#createPublisherForAllAvailable} and
412      * {@link ControlsProviderService#createPublisherForSuggested}.
413      *
414      * It provides the following defaults for non-optional parameters:
415      * <ul>
416      *     <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
417      *     <li> Title: {@code ""}
418      *     <li> Subtitle: {@code ""}
419      * </ul>
420      * This fixes the values relating to state of the {@link Control} as required by
421      * {@link ControlsProviderService#createPublisherForAllAvailable}:
422      * <ul>
423      *     <li> Status: {@link Status#STATUS_UNKNOWN}
424      *     <li> Control template: {@link ControlTemplate#getNoTemplateObject}
425      *     <li> Status text: {@code ""}
426      *     <li> Auth Required: {@code true}
427      * </ul>
428      */
429     @SuppressLint("MutableBareField")
430     public static final class StatelessBuilder {
431         private static final String TAG = "StatelessBuilder";
432         private @NonNull String mControlId;
433         private @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
434         private @NonNull CharSequence mTitle = "";
435         private @NonNull CharSequence mSubtitle = "";
436         private @Nullable CharSequence mStructure;
437         private @Nullable CharSequence mZone;
438         private @NonNull PendingIntent mAppIntent;
439         private @Nullable Icon mCustomIcon;
440         private @Nullable ColorStateList mCustomColor;
441 
442         /**
443          * @param controlId the identifier for the {@link Control}
444          * @param appIntent the pending intent linking to the device Activity
445          */
StatelessBuilder(@onNull String controlId, @NonNull PendingIntent appIntent)446         public StatelessBuilder(@NonNull String controlId,
447                 @NonNull PendingIntent appIntent) {
448             Preconditions.checkNotNull(controlId);
449             Preconditions.checkNotNull(appIntent);
450             mControlId = controlId;
451             mAppIntent = appIntent;
452         }
453 
454         /**
455          * Creates a {@link StatelessBuilder} using an existing {@link Control} as a base.
456          *
457          * @param control base for the builder.
458          */
StatelessBuilder(@onNull Control control)459         public StatelessBuilder(@NonNull Control control) {
460             Preconditions.checkNotNull(control);
461             mControlId = control.mControlId;
462             mDeviceType = control.mDeviceType;
463             mTitle = control.mTitle;
464             mSubtitle = control.mSubtitle;
465             mStructure = control.mStructure;
466             mZone = control.mZone;
467             mAppIntent = control.mAppIntent;
468             mCustomIcon = control.mCustomIcon;
469             mCustomColor = control.mCustomColor;
470         }
471 
472         /**
473          * @param controlId the identifier for the {@link Control}
474          * @return {@code this}
475          */
476         @NonNull
setControlId(@onNull String controlId)477         public StatelessBuilder setControlId(@NonNull String controlId) {
478             Preconditions.checkNotNull(controlId);
479             mControlId = controlId;
480             return this;
481         }
482 
483         /**
484          * @param deviceType type of device represented by this {@link Control}, used to
485          *                   determine the default icon and color
486          * @return {@code this}
487          */
488         @NonNull
setDeviceType(@eviceTypes.DeviceType int deviceType)489         public StatelessBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
490             if (!DeviceTypes.validDeviceType(deviceType)) {
491                 Log.e(TAG, "Invalid device type:" + deviceType);
492                 mDeviceType = DeviceTypes.TYPE_UNKNOWN;
493             } else {
494                 mDeviceType = deviceType;
495             }
496             return this;
497         }
498 
499         /**
500          * @param title the user facing name of the {@link Control}
501          * @return {@code this}
502          */
503         @NonNull
setTitle(@onNull CharSequence title)504         public StatelessBuilder setTitle(@NonNull CharSequence title) {
505             Preconditions.checkNotNull(title);
506             mTitle = title;
507             return this;
508         }
509 
510         /**
511          * @param subtitle additional information about the {@link Control}, to appear underneath
512          *                 the title
513          * @return {@code this}
514          */
515         @NonNull
setSubtitle(@onNull CharSequence subtitle)516         public StatelessBuilder setSubtitle(@NonNull CharSequence subtitle) {
517             Preconditions.checkNotNull(subtitle);
518             mSubtitle = subtitle;
519             return this;
520         }
521 
522         /**
523          * Optional top-level group to help define the {@link Control}'s location, visible to the
524          * user. If not present, the application name will be used as the top-level group. A
525          * structure contains zones which contains controls.
526          *
527          * @param structure name of the structure containing the control
528          * @return {@code this}
529          */
530         @NonNull
setStructure(@ullable CharSequence structure)531         public StatelessBuilder setStructure(@Nullable CharSequence structure) {
532             mStructure = structure;
533             return this;
534         }
535 
536         /**
537          * Optional group name to help define the {@link Control}'s location within a structure,
538          * visible to the user. A structure contains zones which contains controls.
539          *
540          * @param zone name of the zone containing the control
541          * @return {@code this}
542          */
543         @NonNull
setZone(@ullable CharSequence zone)544         public StatelessBuilder setZone(@Nullable CharSequence zone) {
545             mZone = zone;
546             return this;
547         }
548 
549         /**
550          * @param appIntent a {@link PendingIntent} linking to an Activity for the {@link Control}
551          * @return {@code this}
552          */
553         @NonNull
setAppIntent(@onNull PendingIntent appIntent)554         public StatelessBuilder setAppIntent(@NonNull PendingIntent appIntent) {
555             Preconditions.checkNotNull(appIntent);
556             mAppIntent = appIntent;
557             return this;
558         }
559 
560         /**
561          * Optional icon to be shown with the {@link Control}. It is highly recommended
562          * to let the system default the icon unless the default icon is not suitable.
563          *
564          * @param customIcon icon to show
565          * @return {@code this}
566          */
567         @NonNull
setCustomIcon(@ullable Icon customIcon)568         public StatelessBuilder setCustomIcon(@Nullable Icon customIcon) {
569             mCustomIcon = customIcon;
570             return this;
571         }
572 
573         /**
574          * Optional color to be shown with the {@link Control}. It is highly recommended
575          * to let the system default the color unless the default is not suitable for the
576          * application.
577          *
578          * @param customColor background color to use
579          * @return {@code this}
580          */
581         @NonNull
setCustomColor(@ullable ColorStateList customColor)582         public StatelessBuilder setCustomColor(@Nullable ColorStateList customColor) {
583             mCustomColor = customColor;
584             return this;
585         }
586 
587         /**
588          * @return a valid {@link Control}
589          */
590         @NonNull
build()591         public Control build() {
592             return new Control(mControlId,
593                     mDeviceType,
594                     mTitle,
595                     mSubtitle,
596                     mStructure,
597                     mZone,
598                     mAppIntent,
599                     mCustomIcon,
600                     mCustomColor,
601                     STATUS_UNKNOWN,
602                     ControlTemplate.NO_TEMPLATE,
603                     "",
604                     true /* authRequired */);
605         }
606     }
607 
608     /**
609      * Builder class for {@link Control} that contains state information.
610      *
611      * State information is passed through an instance of a {@link ControlTemplate} and will
612      * determine how the user can interact with the {@link Control}. User interactions will
613      * be sent through the method call {@link ControlsProviderService#performControlAction}
614      * with an instance of {@link ControlAction} to convey any potential new value.
615      *
616      * Must be used to provide controls for {@link ControlsProviderService#createPublisherFor}.
617      *
618      * It provides the following defaults for non-optional parameters:
619      * <ul>
620      *     <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
621      *     <li> Title: {@code ""}
622      *     <li> Subtitle: {@code ""}
623      *     <li> Status: {@link Status#STATUS_UNKNOWN}
624      *     <li> Control template: {@link ControlTemplate#getNoTemplateObject}
625      *     <li> Status text: {@code ""}
626      *     <li> Auth Required: {@code true}
627      * </ul>
628      */
629     public static final class StatefulBuilder {
630         private static final String TAG = "StatefulBuilder";
631         private @NonNull String mControlId;
632         private @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
633         private @NonNull CharSequence mTitle = "";
634         private @NonNull CharSequence mSubtitle = "";
635         private @Nullable CharSequence mStructure;
636         private @Nullable CharSequence mZone;
637         private @NonNull PendingIntent mAppIntent;
638         private @Nullable Icon mCustomIcon;
639         private @Nullable ColorStateList mCustomColor;
640         private @Status int mStatus = STATUS_UNKNOWN;
641         private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
642         private @NonNull CharSequence mStatusText = "";
643         private boolean mAuthRequired = true;
644 
645         /**
646          * @param controlId the identifier for the {@link Control}.
647          * @param appIntent the pending intent linking to the device Activity.
648          */
StatefulBuilder(@onNull String controlId, @NonNull PendingIntent appIntent)649         public StatefulBuilder(@NonNull String controlId,
650                 @NonNull PendingIntent appIntent) {
651             Preconditions.checkNotNull(controlId);
652             Preconditions.checkNotNull(appIntent);
653             mControlId = controlId;
654             mAppIntent = appIntent;
655         }
656 
657         /**
658          * Creates a {@link StatelessBuilder} using an existing {@link Control} as a base.
659          *
660          * @param control base for the builder.
661          */
StatefulBuilder(@onNull Control control)662         public StatefulBuilder(@NonNull Control control) {
663             Preconditions.checkNotNull(control);
664             mControlId = control.mControlId;
665             mDeviceType = control.mDeviceType;
666             mTitle = control.mTitle;
667             mSubtitle = control.mSubtitle;
668             mStructure = control.mStructure;
669             mZone = control.mZone;
670             mAppIntent = control.mAppIntent;
671             mCustomIcon = control.mCustomIcon;
672             mCustomColor = control.mCustomColor;
673             mStatus = control.mStatus;
674             mControlTemplate = control.mControlTemplate;
675             mStatusText = control.mStatusText;
676             mAuthRequired = control.mAuthRequired;
677         }
678 
679         /**
680          * @param controlId the identifier for the {@link Control}.
681          * @return {@code this}
682          */
683         @NonNull
setControlId(@onNull String controlId)684         public StatefulBuilder setControlId(@NonNull String controlId) {
685             Preconditions.checkNotNull(controlId);
686             mControlId = controlId;
687             return this;
688         }
689 
690         /**
691          * @param deviceType type of device represented by this {@link Control}, used to
692          *                   determine the default icon and color
693          * @return {@code this}
694          */
695         @NonNull
setDeviceType(@eviceTypes.DeviceType int deviceType)696         public StatefulBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
697             if (!DeviceTypes.validDeviceType(deviceType)) {
698                 Log.e(TAG, "Invalid device type:" + deviceType);
699                 mDeviceType = DeviceTypes.TYPE_UNKNOWN;
700             } else {
701                 mDeviceType = deviceType;
702             }
703             return this;
704         }
705 
706         /**
707          * @param title the user facing name of the {@link Control}
708          * @return {@code this}
709          */
710         @NonNull
setTitle(@onNull CharSequence title)711         public StatefulBuilder setTitle(@NonNull CharSequence title) {
712             Preconditions.checkNotNull(title);
713             mTitle = title;
714             return this;
715         }
716 
717         /**
718          * @param subtitle additional information about the {@link Control}, to appear underneath
719          *                 the title
720          * @return {@code this}
721          */
722         @NonNull
setSubtitle(@onNull CharSequence subtitle)723         public StatefulBuilder setSubtitle(@NonNull CharSequence subtitle) {
724             Preconditions.checkNotNull(subtitle);
725             mSubtitle = subtitle;
726             return this;
727         }
728 
729         /**
730          * Optional top-level group to help define the {@link Control}'s location, visible to the
731          * user. If not present, the application name will be used as the top-level group. A
732          * structure contains zones which contains controls.
733          *
734          * @param structure name of the structure containing the control
735          * @return {@code this}
736          */
737         @NonNull
setStructure(@ullable CharSequence structure)738         public StatefulBuilder setStructure(@Nullable CharSequence structure) {
739             mStructure = structure;
740             return this;
741         }
742 
743         /**
744          * Optional group name to help define the {@link Control}'s location within a structure,
745          * visible to the user. A structure contains zones which contains controls.
746          *
747          * @param zone name of the zone containing the control
748          * @return {@code this}
749          */
750         @NonNull
setZone(@ullable CharSequence zone)751         public StatefulBuilder setZone(@Nullable CharSequence zone) {
752             mZone = zone;
753             return this;
754         }
755 
756         /**
757          * @param appIntent a {@link PendingIntent} linking to an Activity for the {@link Control}
758          * @return {@code this}
759          */
760         @NonNull
setAppIntent(@onNull PendingIntent appIntent)761         public StatefulBuilder setAppIntent(@NonNull PendingIntent appIntent) {
762             Preconditions.checkNotNull(appIntent);
763             mAppIntent = appIntent;
764             return this;
765         }
766 
767         /**
768          * Optional icon to be shown with the {@link Control}. It is highly recommended
769          * to let the system default the icon unless the default icon is not suitable.
770          *
771          * @param customIcon icon to show
772          * @return {@code this}
773          */
774         @NonNull
setCustomIcon(@ullable Icon customIcon)775         public StatefulBuilder setCustomIcon(@Nullable Icon customIcon) {
776             mCustomIcon = customIcon;
777             return this;
778         }
779 
780         /**
781          * Optional color to be shown with the {@link Control}. It is highly recommended
782          * to let the system default the color unless the default is not suitable for the
783          * application.
784          *
785          * @param customColor background color to use
786          * @return {@code this}
787          */
788         @NonNull
setCustomColor(@ullable ColorStateList customColor)789         public StatefulBuilder setCustomColor(@Nullable ColorStateList customColor) {
790             mCustomColor = customColor;
791             return this;
792         }
793 
794         /**
795          * @param status status of the {@link Control}, used to convey information about the
796          *               attempt to fetch the current state
797          * @return {@code this}
798          */
799         @NonNull
setStatus(@tatus int status)800         public StatefulBuilder setStatus(@Status int status) {
801             if (status < 0 || status >= NUM_STATUS) {
802                 mStatus = STATUS_UNKNOWN;
803                 Log.e(TAG, "Status unknown:" + status);
804             } else {
805                 mStatus = status;
806             }
807             return this;
808         }
809 
810         /**
811          * Set the {@link ControlTemplate} to define the primary user interaction
812          *
813          * Devices may support a variety of user interactions, and all interactions cannot be
814          * represented with a single {@link ControlTemplate}. Therefore, the selected template
815          * should be most closely aligned with what the expected primary device action will be.
816          * Any secondary interactions can be done via the {@link #setAppIntent(PendingIntent)}.
817          *
818          * @param controlTemplate instance of {@link ControlTemplate}, that defines how the
819          *                        {@link Control} will behave and what interactions are
820          *                        available to the user
821          * @return {@code this}
822          */
823         @NonNull
setControlTemplate(@onNull ControlTemplate controlTemplate)824         public StatefulBuilder setControlTemplate(@NonNull ControlTemplate controlTemplate) {
825             Preconditions.checkNotNull(controlTemplate);
826             mControlTemplate = controlTemplate;
827             return this;
828         }
829 
830         /**
831          * @param statusText user-facing text description of the {@link Control}'s status,
832          *                   describing its current state
833          * @return {@code this}
834          */
835         @NonNull
setStatusText(@onNull CharSequence statusText)836         public StatefulBuilder setStatusText(@NonNull CharSequence statusText) {
837             Preconditions.checkNotNull(statusText);
838             mStatusText = statusText;
839             return this;
840         }
841 
842         /**
843          * @param authRequired true if the control can not be interacted with until the device is
844          *                     unlocked
845          * @return {@code this}
846          */
847         @NonNull
setAuthRequired(boolean authRequired)848         public StatefulBuilder setAuthRequired(boolean authRequired) {
849             mAuthRequired = authRequired;
850             return this;
851         }
852 
853         /**
854          * @return a valid {@link Control}
855          */
856         @NonNull
build()857         public Control build() {
858             return new Control(mControlId,
859                     mDeviceType,
860                     mTitle,
861                     mSubtitle,
862                     mStructure,
863                     mZone,
864                     mAppIntent,
865                     mCustomIcon,
866                     mCustomColor,
867                     mStatus,
868                     mControlTemplate,
869                     mStatusText,
870                     mAuthRequired);
871         }
872     }
873 }
874