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