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