1 /* 2 * Copyright 2018 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 com.android.car.settings.common; 18 19 import static com.android.car.settings.common.PreferenceXmlParser.PREF_AVAILABILITY_STATUS_HIDDEN; 20 import static com.android.car.settings.common.PreferenceXmlParser.PREF_AVAILABILITY_STATUS_READ; 21 22 import android.car.CarOccupantZoneManager; 23 import android.car.drivingstate.CarUxRestrictions; 24 import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener; 25 import android.content.Context; 26 import android.os.SystemClock; 27 import android.widget.Toast; 28 29 import androidx.annotation.IntDef; 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.lifecycle.DefaultLifecycleObserver; 34 import androidx.lifecycle.LifecycleOwner; 35 import androidx.preference.Preference; 36 import androidx.preference.PreferenceGroup; 37 38 import com.android.car.settings.CarSettingsApplication; 39 import com.android.car.settings.R; 40 import com.android.car.ui.preference.ClickableWhileDisabledPreference; 41 import com.android.car.ui.preference.UxRestrictablePreference; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.Arrays; 46 import java.util.HashSet; 47 import java.util.Set; 48 import java.util.function.Consumer; 49 50 /** 51 * Controller which encapsulates the business logic associated with a {@link Preference}. All car 52 * settings controllers should extend this class. 53 * 54 * <p>Controllers are responsible for populating and modifying the presentation of an associated 55 * preference while responding to changes in system state. This is enabled via {@link 56 * SettingsFragment} which registers controllers as observers on its lifecycle and dispatches 57 * {@link CarUxRestrictions} change events to the controllers via the {@link 58 * OnUxRestrictionsChangedListener} interface. 59 * 60 * <p>Controllers should be instantiated from XML. To do so, define a preference and include the 61 * {@code controller} attribute in the preference tag and assign the fully qualified class name. 62 * 63 * <p>For example: 64 * <pre>{@code 65 * <Preference 66 * android:key="my_preference_key" 67 * android:title="@string/my_preference_title" 68 * android:icon="@drawable/ic_settings" 69 * android:fragment="com.android.settings.foo.MyFragment" 70 * settings:controller="com.android.settings.foo.MyPreferenceController"/> 71 * }</pre> 72 * 73 * <p>Subclasses must implement {@link #getPreferenceType()} to define the upper bound type on the 74 * {@link Preference} that the controller is associated with. For example, a bound of {@link 75 * androidx.preference.PreferenceGroup} indicates that the controller will utilize preference group 76 * methods in its operation. {@link #setPreference(Preference)} will throw an {@link 77 * IllegalArgumentException} if not passed a subclass of the upper bound type. 78 * 79 * <p>Subclasses may implement any or all of the following methods (see method Javadocs for more 80 * information): 81 * 82 * <ul> 83 * <li>{@link #checkInitialized()} 84 * <li>{@link #onCreateInternal()} 85 * <li>{@link #getAvailabilityStatus()} 86 * <li>{@link #onStartInternal()} 87 * <li>{@link #onResumeInternal()} 88 * <li>{@link #onPauseInternal()} 89 * <li>{@link #onStopInternal()} 90 * <li>{@link #onDestroyInternal()} 91 * <li>{@link #updateState(Preference)} 92 * <li>{@link #onApplyUxRestrictions(CarUxRestrictions)} 93 * <li>{@link #handlePreferenceChanged(Preference, Object)} 94 * <li>{@link #handlePreferenceClicked(Preference)} 95 * </ul> 96 * 97 * @param <V> the upper bound on the type of {@link Preference} on which the controller 98 * expects to operate. 99 */ 100 public abstract class PreferenceController<V extends Preference> implements 101 DefaultLifecycleObserver, 102 OnUxRestrictionsChangedListener { 103 private static final Logger LOG = new Logger(PreferenceController.class); 104 105 /** 106 * Denotes the availability of a setting. 107 * 108 * @see #getAvailabilityStatus() 109 */ 110 @Retention(RetentionPolicy.SOURCE) 111 @IntDef({AVAILABLE, CONDITIONALLY_UNAVAILABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_PROFILE, 112 AVAILABLE_FOR_VIEWING}) 113 public @interface AvailabilityStatus { 114 } 115 116 /** 117 * The setting is available. 118 */ 119 public static final int AVAILABLE = 0; 120 121 /** 122 * The setting is currently unavailable but may become available in the future. Use 123 * {@link #DISABLED_FOR_PROFILE} if it describes the condition more accurately. 124 */ 125 public static final int CONDITIONALLY_UNAVAILABLE = 1; 126 127 /** 128 * The setting is not and will not be supported by this device. 129 */ 130 public static final int UNSUPPORTED_ON_DEVICE = 2; 131 132 /** 133 * The setting cannot be changed by the current profile. 134 */ 135 public static final int DISABLED_FOR_PROFILE = 3; 136 137 /** 138 * The setting cannot be changed. 139 */ 140 public static final int AVAILABLE_FOR_VIEWING = 4; 141 142 /** 143 * Denotes the availability of a setting for the current zone. 144 * 145 * @see #getAvailabilityStatusForZone() 146 */ 147 @Retention(RetentionPolicy.SOURCE) 148 @IntDef({AVAILABLE_FOR_ZONE, AVAILABLE_FOR_VIEWING_FOR_ZONE, HIDDEN_FOR_ZONE}) 149 public @interface AvailabilityStatusForZone { 150 } 151 152 /** 153 * The setting is available for this zone 154 */ 155 public static final int AVAILABLE_FOR_ZONE = 0; 156 /** 157 * The setting cannot be changed for this zone 158 */ 159 public static final int AVAILABLE_FOR_VIEWING_FOR_ZONE = 1; 160 /** 161 * The setting is hidden for this zone. 162 */ 163 public static final int HIDDEN_FOR_ZONE = 2; 164 165 /** 166 * Indicates whether all Preferences are configured to ignore UX Restrictions Event. 167 */ 168 private final boolean mAlwaysIgnoreUxRestrictions; 169 170 /** 171 * Set of the keys of Preferences that ignore UX Restrictions. When mAlwaysIgnoreUxRestrictions 172 * is configured to be false, then only the Preferences whose keys are contained in this Set 173 * ignore UX Restrictions. 174 */ 175 private final Set<String> mPreferencesIgnoringUxRestrictions; 176 177 private final Context mContext; 178 private final String mPreferenceKey; 179 private final FragmentController mFragmentController; 180 private final String mRestrictedWhileDrivingMessage; 181 private final String mRestrictedForDriversMessage; 182 private final String mRestrictedForPassengersMessage; 183 private final int mDebounceIntervalMs; 184 185 private CarUxRestrictions mUxRestrictions; 186 private V mPreference; 187 private boolean mIsCreated; 188 private boolean mIsStarted; 189 private long mDebounceStartTimeMs; 190 private int mAvailabilityStatusForZone; 191 192 /** 193 * Controllers should be instantiated from XML. To pass additional arguments see 194 * {@link SettingsFragment#use(Class, int)}. 195 */ PreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)196 public PreferenceController(Context context, String preferenceKey, 197 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 198 mContext = context; 199 mPreferenceKey = preferenceKey; 200 mFragmentController = fragmentController; 201 mUxRestrictions = uxRestrictions; 202 mPreferencesIgnoringUxRestrictions = new HashSet<String>(Arrays.asList( 203 mContext.getResources().getStringArray(R.array.config_ignore_ux_restrictions))); 204 mAlwaysIgnoreUxRestrictions = 205 mContext.getResources().getBoolean(R.bool.config_always_ignore_ux_restrictions); 206 mRestrictedWhileDrivingMessage = 207 mContext.getResources().getString(R.string.car_ui_restricted_while_driving); 208 mRestrictedForDriversMessage = 209 mContext.getResources().getString(R.string.restricted_for_driver); 210 mRestrictedForPassengersMessage = 211 mContext.getResources().getString(R.string.restricted_for_passenger); 212 mDebounceIntervalMs = 213 mContext.getResources().getInteger(R.integer.config_preference_onclick_debounce_ms); 214 } 215 216 /** 217 * Sets the setting's availabilityStatus for this zone. 218 * Defaults to {@link #AVAILABLE_FOR_ZONE}. 219 */ 220 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) setAvailabilityStatusForZone(@ullable String availabilityStatusForZone)221 public final void setAvailabilityStatusForZone(@Nullable String availabilityStatusForZone) { 222 if (PREF_AVAILABILITY_STATUS_READ.equals(availabilityStatusForZone)) { 223 mAvailabilityStatusForZone = AVAILABLE_FOR_VIEWING_FOR_ZONE; 224 } else if (PREF_AVAILABILITY_STATUS_HIDDEN.equals(availabilityStatusForZone)) { 225 mAvailabilityStatusForZone = HIDDEN_FOR_ZONE; 226 } else { 227 mAvailabilityStatusForZone = AVAILABLE_FOR_ZONE; 228 } 229 } 230 231 /** 232 * Returns the context used to construct the controller. 233 */ getContext()234 protected final Context getContext() { 235 return mContext; 236 } 237 238 /** 239 * Returns the key for the preference managed by this controller set at construction. 240 */ getPreferenceKey()241 protected final String getPreferenceKey() { 242 return mPreferenceKey; 243 } 244 245 /** 246 * Returns the {@link FragmentController} used to launch fragments and go back to previous 247 * fragments. This is set at construction. 248 */ getFragmentController()249 protected final FragmentController getFragmentController() { 250 return mFragmentController; 251 } 252 253 /** 254 * Returns the current {@link CarUxRestrictions} applied to the controller. Subclasses may use 255 * this to limit which content is displayed in the associated preference. May be called anytime. 256 */ getUxRestrictions()257 protected final CarUxRestrictions getUxRestrictions() { 258 return mUxRestrictions; 259 } 260 261 /** 262 * Returns the preference associated with this controller. This may be used in any of the 263 * lifecycle methods, as the preference is set before they are called.. 264 */ getPreference()265 protected final V getPreference() { 266 return mPreference; 267 } 268 269 /** 270 * Called by {@link SettingsFragment} to associate the controller with its preference after the 271 * screen is created. This is guaranteed to be called before {@link #onCreateInternal()}. 272 * 273 * @throws IllegalArgumentException if the given preference does not match the type 274 * returned by {@link #getPreferenceType()} 275 * @throws IllegalStateException if subclass defined initialization is not 276 * complete. 277 */ setPreference(Preference preference)278 final void setPreference(Preference preference) { 279 PreferenceUtil.requirePreferenceType(preference, getPreferenceType()); 280 mPreference = getPreferenceType().cast(preference); 281 mPreference.setOnPreferenceChangeListener( 282 (changedPref, newValue) -> handlePreferenceChanged( 283 getPreferenceType().cast(changedPref), newValue)); 284 mPreference.setOnPreferenceClickListener( 285 clickedPref -> { 286 // Debounce onClick() calls 287 long curTime = SystemClock.elapsedRealtime(); 288 if (mDebounceStartTimeMs != 0 289 && curTime < (mDebounceStartTimeMs + mDebounceIntervalMs)) { 290 LOG.i("OnClick event dropped due to debouncing"); 291 return true; 292 } 293 mDebounceStartTimeMs = curTime; 294 return handlePreferenceClicked(getPreferenceType().cast(clickedPref)); 295 }); 296 checkInitialized(); 297 } 298 299 /** 300 * Called by {@link SettingsFragment} to notify that the applied ux restrictions have changed. 301 * The controller will refresh its UI accordingly unless it is not yet created. In that case, 302 * the UI will refresh once created. 303 */ 304 @Override onUxRestrictionsChanged(CarUxRestrictions uxRestrictions)305 public final void onUxRestrictionsChanged(CarUxRestrictions uxRestrictions) { 306 mUxRestrictions = uxRestrictions; 307 refreshUi(); 308 } 309 310 /** 311 * Updates the preference presentation based on its {@link #getAvailabilityStatus()} status. If 312 * the controller is available, the associated preference is shown and a call to {@link 313 * #updateState(Preference)} and {@link #onApplyUxRestrictions(CarUxRestrictions)} are 314 * dispatched to allow the controller to modify the presentation for the current state. If the 315 * controller is not available, the associated preference is hidden from the screen. This is a 316 * no-op if the controller is not yet created. 317 */ refreshUi()318 public final void refreshUi() { 319 if (!mIsCreated) { 320 return; 321 } 322 323 if (isAvailable()) { 324 mPreference.setVisible(true); 325 mPreference.setEnabled(getAvailabilityStatus() != AVAILABLE_FOR_VIEWING); 326 updateState(mPreference); 327 onApplyUxRestrictions(mUxRestrictions); 328 } else { 329 mPreference.setVisible(false); 330 } 331 } 332 isAvailable()333 private boolean isAvailable() { 334 int availabilityStatus = getAvailabilityStatus(); 335 return availabilityStatus == AVAILABLE || availabilityStatus == AVAILABLE_FOR_VIEWING; 336 } 337 338 // Controller lifecycle ======================================================================== 339 340 /** 341 * Dispatches a call to {@link #onCreateInternal()} and {@link #refreshUi()} to enable 342 * controllers to setup initial state before a preference is visible. If the controller is 343 * {@link #UNSUPPORTED_ON_DEVICE}, the preference is hidden and no further action is taken. 344 */ 345 @Override onCreate(@onNull LifecycleOwner owner)346 public final void onCreate(@NonNull LifecycleOwner owner) { 347 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 348 mPreference.setVisible(false); 349 return; 350 } 351 onCreateInternal(); 352 if (isPreferenceDisabledForZone()) { 353 setClickableWhileDisabledInternal(getPreference(), /* clickable= */ true, 354 getZoneDisabledPreferenceOnClick()); 355 } 356 mIsCreated = true; 357 refreshUi(); 358 } 359 360 /** 361 * Dispatches a call to {@link #onStartInternal()} and {@link #refreshUi()} to account for any 362 * state changes that may have occurred while the controller was stopped. Returns immediately 363 * if the controller is {@link #UNSUPPORTED_ON_DEVICE}. 364 */ 365 @Override onStart(@onNull LifecycleOwner owner)366 public final void onStart(@NonNull LifecycleOwner owner) { 367 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 368 return; 369 } 370 onStartInternal(); 371 mIsStarted = true; 372 refreshUi(); 373 } 374 375 /** 376 * Notifies that the controller is resumed by dispatching a call to {@link #onResumeInternal()}. 377 * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}. 378 */ 379 @Override onResume(@onNull LifecycleOwner owner)380 public final void onResume(@NonNull LifecycleOwner owner) { 381 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 382 return; 383 } 384 onResumeInternal(); 385 } 386 387 /** 388 * Notifies that the controller is paused by dispatching a call to {@link #onPauseInternal()}. 389 * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}. 390 */ 391 @Override onPause(@onNull LifecycleOwner owner)392 public final void onPause(@NonNull LifecycleOwner owner) { 393 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 394 return; 395 } 396 onPauseInternal(); 397 } 398 399 /** 400 * Notifies that the controller is stopped by dispatching a call to {@link #onStopInternal()}. 401 * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}. 402 */ 403 @Override onStop(@onNull LifecycleOwner owner)404 public final void onStop(@NonNull LifecycleOwner owner) { 405 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 406 return; 407 } 408 mIsStarted = false; 409 onStopInternal(); 410 } 411 412 /** 413 * Notifies that the controller is destroyed by dispatching a call to {@link 414 * #onDestroyInternal()}. Returns immediately if the controller is 415 * {@link #UNSUPPORTED_ON_DEVICE}. 416 */ 417 @Override onDestroy(@onNull LifecycleOwner owner)418 public final void onDestroy(@NonNull LifecycleOwner owner) { 419 if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) { 420 return; 421 } 422 mIsCreated = false; 423 onDestroyInternal(); 424 } 425 426 // Methods for override ======================================================================== 427 428 /** 429 * Returns the upper bound type of the preference on which this controller will operate. 430 */ getPreferenceType()431 protected abstract Class<V> getPreferenceType(); 432 433 /** 434 * Subclasses may override this method to throw {@link IllegalStateException} if any expected 435 * post-instantiation setup is not completed using {@link SettingsFragment#use(Class, int)} 436 * prior to associating the controller with its preference. This will be called before the 437 * controller lifecycle begins. 438 */ checkInitialized()439 protected void checkInitialized() { 440 } 441 442 /** 443 * Returns the {@link AvailabilityStatus} for the setting. This status is used as the final 444 * result to determine if the setting should be shown, hidden, or disabled. Defaults to 445 * {@link #AVAILABLE}. It is determined by considering the return value of 446 * {@link #getDefaultAvailabilityStatus()} and the availabilityStatus for zone with and the 447 * availabilityStatus for the current CarOccupantZone of the display where Settings are shown. 448 * This will be called before the controller lifecycle begins and on refresh events. 449 */ 450 @AvailabilityStatus 451 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) getAvailabilityStatus()452 public final int getAvailabilityStatus() { 453 int defaultStatus = getDefaultAvailabilityStatus(); 454 switch (defaultStatus) { 455 case CONDITIONALLY_UNAVAILABLE: // fall through 456 case UNSUPPORTED_ON_DEVICE: // fall through 457 case DISABLED_FOR_PROFILE: 458 return defaultStatus; 459 case AVAILABLE_FOR_VIEWING: 460 switch (mAvailabilityStatusForZone) { 461 case HIDDEN_FOR_ZONE: 462 return CONDITIONALLY_UNAVAILABLE; 463 case AVAILABLE_FOR_ZONE: // fall through 464 case AVAILABLE_FOR_VIEWING_FOR_ZONE: // fall through 465 default: 466 return AVAILABLE_FOR_VIEWING; 467 } 468 case AVAILABLE: // fall through 469 default: 470 switch (mAvailabilityStatusForZone) { 471 case AVAILABLE_FOR_VIEWING_FOR_ZONE: 472 return AVAILABLE_FOR_VIEWING; 473 case HIDDEN_FOR_ZONE: 474 return CONDITIONALLY_UNAVAILABLE; 475 case AVAILABLE_FOR_ZONE: // fall through 476 default: 477 return AVAILABLE; 478 } 479 } 480 } 481 482 /** 483 * Returns the {@link AvailabilityStatus} for the setting. This status is used 484 * with the availabilityStatus for zone within {@link #getAvailabilityStatus()} to determine 485 * if the setting should be shown, hidden, or disabled according to menu settings. 486 * Defaults to {@link #AVAILABLE}. 487 */ 488 @AvailabilityStatus getDefaultAvailabilityStatus()489 protected int getDefaultAvailabilityStatus() { 490 return AVAILABLE; 491 } 492 493 /** 494 * Subclasses may override this method to complete any operations needed at creation time e.g. 495 * loading static configuration. 496 * 497 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 498 */ onCreateInternal()499 protected void onCreateInternal() { 500 } 501 502 /** 503 * Subclasses may override this method to complete any operations needed each time the 504 * controller is started e.g. registering broadcast receivers. 505 * 506 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 507 */ onStartInternal()508 protected void onStartInternal() { 509 } 510 511 /** 512 * Subclasses may override this method to complete any operations needed each time the 513 * controller is resumed. Prefer to use {@link #onStartInternal()} unless absolutely necessary 514 * as controllers may not be resumed in a multi-display scenario. 515 * 516 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 517 */ onResumeInternal()518 protected void onResumeInternal() { 519 } 520 521 /** 522 * Subclasses may override this method to complete any operations needed each time the 523 * controller is paused. Prefer to use {@link #onStartInternal()} unless absolutely necessary 524 * as controllers may not be resumed in a multi-display scenario. 525 * 526 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 527 */ onPauseInternal()528 protected void onPauseInternal() { 529 } 530 531 /** 532 * Subclasses may override this method to complete any operations needed each time the 533 * controller is stopped e.g. unregistering broadcast receivers. 534 * 535 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 536 */ onStopInternal()537 protected void onStopInternal() { 538 } 539 540 /** 541 * Subclasses may override this method to complete any operations needed when the controller is 542 * destroyed e.g. freeing up held resources. 543 * 544 * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers. 545 */ onDestroyInternal()546 protected void onDestroyInternal() { 547 } 548 549 /** 550 * Subclasses may override this method to update the presentation of the preference for the 551 * current system state (summary, switch state, etc). If the preference has dynamic content 552 * (such as preferences added to a group), it may be updated here as well. 553 * 554 * <p>Important: Operations should be idempotent as this may be called multiple times. 555 * 556 * <p>Note: this will only be called when the following are true: 557 * <ul> 558 * <li>{@link #getAvailabilityStatus()} returns {@link #AVAILABLE} 559 * <li>{@link #onCreateInternal()} has completed. 560 * </ul> 561 */ updateState(V preference)562 protected void updateState(V preference) { 563 } 564 565 /** 566 * Updates the preference enabled status given the {@code restrictionInfo}. This will be called 567 * before the controller lifecycle begins and on refresh events. The preference is disabled by 568 * default when {@link CarUxRestrictions#UX_RESTRICTIONS_NO_SETUP} is set in {@code 569 * uxRestrictions}. Subclasses may override this method to modify enabled state based on 570 * additional driving restrictions. 571 */ onApplyUxRestrictions(CarUxRestrictions uxRestrictions)572 protected void onApplyUxRestrictions(CarUxRestrictions uxRestrictions) { 573 boolean restrict = shouldApplyUxRestrictions(uxRestrictions); 574 575 restrictPreference(mPreference, restrict); 576 } 577 578 /** 579 * Decides whether or not this {@link PreferenceController} should apply {@code uxRestrictions} 580 * based on the type of restrictions currently present, and the value of the {@code 581 * config_always_ignore_ux_restrictions} and 582 * {@code config_ignore_ux_restrictions} config flags. 583 * <p> 584 * It is not expected that subclasses will override this functionality. If they do, it is 585 * important to respect the config flags being consulted here. 586 * 587 * @return true if {@code uxRestrictions} should be applied and false otherwise. 588 */ shouldApplyUxRestrictions(CarUxRestrictions uxRestrictions)589 protected boolean shouldApplyUxRestrictions(CarUxRestrictions uxRestrictions) { 590 return !isUxRestrictionsIgnored(mAlwaysIgnoreUxRestrictions, 591 mPreferencesIgnoringUxRestrictions) 592 && CarUxRestrictionsHelper.isNoSetup(uxRestrictions) 593 && getAvailabilityStatus() != AVAILABLE_FOR_VIEWING; 594 } 595 596 /** 597 * Updates the UxRestricted state and action for a preference. This will also update all child 598 * preferences with the same state and action when {@param preference} is a PreferenceGroup. 599 * 600 * @param preference the preference to update 601 * @param restrict whether or not the preference should be restricted 602 */ restrictPreference(Preference preference, boolean restrict)603 protected void restrictPreference(Preference preference, boolean restrict) { 604 if (preference instanceof UxRestrictablePreference) { 605 UxRestrictablePreference restrictablePreference = (UxRestrictablePreference) preference; 606 restrictablePreference.setUxRestricted(restrict); 607 restrictablePreference.setOnClickWhileRestrictedListener(p -> 608 Toast.makeText(mContext, mRestrictedWhileDrivingMessage, 609 Toast.LENGTH_LONG).show()); 610 } 611 if (preference instanceof PreferenceGroup) { 612 PreferenceGroup preferenceGroup = (PreferenceGroup) preference; 613 for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) { 614 restrictPreference(preferenceGroup.getPreference(i), restrict); 615 } 616 } 617 } 618 619 /** 620 * Updates the clickable while disabled state and action for a preference. This will also 621 * update all child preferences with the same state and action when {@param preference} 622 * is a PreferenceGroup. If the preference is only available for viewing for the zone, 623 * this won't apply since an action will have already been assigned. 624 * 625 * @param preference the preference to update 626 * @param clickable whether or not the preference should be clickable when disabled 627 * @param disabledClickAction the action that should be taken when clicked while disabled. 628 */ setClickableWhileDisabled(Preference preference, boolean clickable, @Nullable Consumer<Preference> disabledClickAction)629 protected void setClickableWhileDisabled(Preference preference, boolean clickable, 630 @Nullable Consumer<Preference> disabledClickAction) { 631 // Preferences disabled for zone message has highest priority 632 if (isPreferenceDisabledForZone()) { 633 return; 634 } 635 setClickableWhileDisabledInternal(preference, clickable, disabledClickAction); 636 } 637 setClickableWhileDisabledInternal(Preference preference, boolean clickable, @Nullable Consumer<Preference> disabledClickAction)638 private void setClickableWhileDisabledInternal(Preference preference, boolean clickable, 639 @Nullable Consumer<Preference> disabledClickAction) { 640 if (preference instanceof ClickableWhileDisabledPreference) { 641 ClickableWhileDisabledPreference pref = (ClickableWhileDisabledPreference) preference; 642 pref.setClickableWhileDisabled(clickable); 643 pref.setDisabledClickListener(disabledClickAction); 644 } 645 if (preference instanceof PreferenceGroup) { 646 PreferenceGroup preferenceGroup = (PreferenceGroup) preference; 647 for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) { 648 setClickableWhileDisabledInternal(preferenceGroup.getPreference(i), clickable, 649 disabledClickAction); 650 } 651 } 652 } 653 654 /** 655 * Called when the associated preference is changed by the user. This is called before the state 656 * of the preference is updated and before the state is persisted. 657 * 658 * @param preference the changed preference. 659 * @param newValue the new value of the preference. 660 * @return {@code true} to update the state of the preference with the new value. Defaults to 661 * {@code true}. 662 */ handlePreferenceChanged(V preference, Object newValue)663 protected boolean handlePreferenceChanged(V preference, Object newValue) { 664 return true; 665 } 666 667 /** 668 * Called when the preference associated with this controller is clicked. Subclasses may 669 * choose to handle the click event. 670 * 671 * @param preference the clicked preference. 672 * @return {@code true} if click is handled and further propagation should cease. Defaults to 673 * {@code false}. 674 */ handlePreferenceClicked(V preference)675 protected boolean handlePreferenceClicked(V preference) { 676 return false; 677 } 678 isUxRestrictionsIgnored(boolean allIgnores, Set prefsThatIgnore)679 protected boolean isUxRestrictionsIgnored(boolean allIgnores, Set prefsThatIgnore) { 680 return allIgnores || prefsThatIgnore.contains(mPreferenceKey); 681 } 682 isStarted()683 protected final boolean isStarted() { 684 return mIsStarted; 685 } 686 getZoneDisabledPreferenceOnClick()687 private Consumer<Preference> getZoneDisabledPreferenceOnClick() { 688 int zoneType = ((CarSettingsApplication) getContext().getApplicationContext()) 689 .getMyOccupantZoneType(); 690 String message = zoneType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER 691 ? mRestrictedForDriversMessage : mRestrictedForPassengersMessage; 692 return p -> Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 693 } 694 isPreferenceDisabledForZone()695 private boolean isPreferenceDisabledForZone() { 696 return mAvailabilityStatusForZone == AVAILABLE_FOR_VIEWING_FOR_ZONE 697 && (getDefaultAvailabilityStatus() == AVAILABLE_FOR_VIEWING 698 || getDefaultAvailabilityStatus() == AVAILABLE); 699 } 700 } 701