File: Control.java

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 326,092 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,181; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 117; sed: 19
file content (873 lines) | stat: -rw-r--r-- 30,992 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
/*
 * 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 {};

    /**
     * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is
     * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes
     * to indicate the proper device state.
     */
    public static final int STATUS_UNKNOWN = 0;

    /**
     * Used to indicate that the state of the device was successfully retrieved. This includes
     * all scenarios where the device may have a warning for the user, such as "Lock jammed",
     * or "Vacuum stuck". Any information for the user should be set through
     * {@link StatefulBuilder#setStatusText}.
     */
    public static final int STATUS_OK = 1;

    /**
     * The device corresponding to the {@link Control} cannot be found or was removed. The user
     * will be alerted and directed to the application to resolve.
     */
    public static final int STATUS_NOT_FOUND = 2;

    /**
     * Used to indicate that there was a temporary error while loading the device state. A default
     * error message will be displayed in place of any custom text that was set through
     * {@link StatefulBuilder#setStatusText}.
     */
    public static final int STATUS_ERROR = 3;

    /**
     * The {@link Control} is currently disabled.  A default error message will be displayed in
     * place of any custom text that was set through {@link StatefulBuilder#setStatusText}.
     */
    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;
    private final boolean mAuthRequired;

    /**
     * @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
     * @param authRequired true if the control can not be interacted with until the device is
     *                     unlocked
     */
    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,
            boolean authRequired) {
        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;
        mAuthRequired = authRequired;
    }

    /**
     * @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();
        mAuthRequired = in.readBoolean();
    }

    /**
     * @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;
    }

    /**
     * @return true if the control can not be interacted with until the device is unlocked
     */
    public boolean isAuthRequired() {
        return mAuthRequired;
    }

    @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);
        dest.writeBoolean(mAuthRequired);
    }

    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 ""}
     *     <li> Auth Required: {@code true}
     * </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,
                    "",
                    true /* authRequired */);
        }
    }

    /**
     * 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 ""}
     *     <li> Auth Required: {@code true}
     * </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 = "";
        private boolean mAuthRequired = true;

        /**
         * @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;
            mAuthRequired = control.mAuthRequired;
        }

        /**
         * @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;
        }

        /**
         * Set the {@link ControlTemplate} to define the primary user interaction
         *
         * Devices may support a variety of user interactions, and all interactions cannot be
         * represented with a single {@link ControlTemplate}. Therefore, the selected template
         * should be most closely aligned with what the expected primary device action will be.
         * Any secondary interactions can be done via the {@link #setAppIntent(PendingIntent)}.
         *
         * @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;
        }

        /**
         * @param authRequired true if the control can not be interacted with until the device is
         *                     unlocked
         * @return {@code this}
         */
        @NonNull
        public StatefulBuilder setAuthRequired(boolean authRequired) {
            mAuthRequired = authRequired;
            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,
                    mAuthRequired);
        }
    }
}