Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 package com.android.settings.core;
     15 
     16 import android.annotation.IntDef;
     17 import android.content.Context;
     18 import android.content.IntentFilter;
     19 import android.text.TextUtils;
     20 import android.util.Log;
     21 
     22 import com.android.settings.search.ResultPayload;
     23 import com.android.settings.search.SearchIndexableRaw;
     24 import com.android.settings.slices.SliceData;
     25 import com.android.settingslib.core.AbstractPreferenceController;
     26 
     27 import java.lang.annotation.Retention;
     28 import java.lang.annotation.RetentionPolicy;
     29 import java.lang.reflect.Constructor;
     30 import java.lang.reflect.InvocationTargetException;
     31 import java.util.List;
     32 
     33 import android.support.v7.preference.Preference;
     34 import android.support.v7.preference.PreferenceGroup;
     35 import android.support.v7.preference.PreferenceScreen;
     36 
     37 /**
     38  * Abstract class to consolidate utility between preference controllers and act as an interface
     39  * for Slices. The abstract classes that inherit from this class will act as the direct interfaces
     40  * for each type when plugging into Slices.
     41  */
     42 public abstract class BasePreferenceController extends AbstractPreferenceController {
     43 
     44     private static final String TAG = "SettingsPrefController";
     45 
     46     /**
     47      * Denotes the availability of the Setting.
     48      * <p>
     49      * Used both explicitly and by the convenience methods {@link #isAvailable()} and
     50      * {@link #isSupported()}.
     51      */
     52     @Retention(RetentionPolicy.SOURCE)
     53     @IntDef({AVAILABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_USER, DISABLED_DEPENDENT_SETTING,
     54             CONDITIONALLY_UNAVAILABLE})
     55     public @interface AvailabilityStatus {
     56     }
     57 
     58     /**
     59      * The setting is available.
     60      */
     61     public static final int AVAILABLE = 0;
     62 
     63     /**
     64      * A generic catch for settings which are currently unavailable, but may become available in
     65      * the future. You should use {@link #DISABLED_FOR_USER} or {@link #DISABLED_DEPENDENT_SETTING}
     66      * if they describe the condition more accurately.
     67      */
     68     public static final int CONDITIONALLY_UNAVAILABLE = 1;
     69 
     70     /**
     71      * The setting is not, and will not supported by this device.
     72      * <p>
     73      * There is no guarantee that the setting page exists, and any links to the Setting should take
     74      * you to the home page of Settings.
     75      */
     76     public static final int UNSUPPORTED_ON_DEVICE = 2;
     77 
     78 
     79     /**
     80      * The setting cannot be changed by the current user.
     81      * <p>
     82      * Links to the Setting should take you to the page of the Setting, even if it cannot be
     83      * changed.
     84      */
     85     public static final int DISABLED_FOR_USER = 3;
     86 
     87     /**
     88      * The setting has a dependency in the Settings App which is currently blocking access.
     89      * <p>
     90      * It must be possible for the Setting to be enabled by changing the configuration of the device
     91      * settings. That is, a setting that cannot be changed because of the state of another setting.
     92      * This should not be used for a setting that would be hidden from the UI entirely.
     93      * <p>
     94      * Correct use: Intensity of night display should be {@link #DISABLED_DEPENDENT_SETTING} when
     95      * night display is off.
     96      * Incorrect use: Mobile Data is {@link #DISABLED_DEPENDENT_SETTING} when there is no
     97      * data-enabled sim.
     98      * <p>
     99      * Links to the Setting should take you to the page of the Setting, even if it cannot be
    100      * changed.
    101      */
    102     public static final int DISABLED_DEPENDENT_SETTING = 4;
    103 
    104 
    105     protected final String mPreferenceKey;
    106 
    107     /**
    108      * Instantiate a controller as specified controller type and user-defined key.
    109      * <p/>
    110      * This is done through reflection. Do not use this method unless you know what you are doing.
    111      */
    112     public static BasePreferenceController createInstance(Context context,
    113             String controllerName, String key) {
    114         try {
    115             final Class<?> clazz = Class.forName(controllerName);
    116             final Constructor<?> preferenceConstructor =
    117                     clazz.getConstructor(Context.class, String.class);
    118             final Object[] params = new Object[] {context, key};
    119             return (BasePreferenceController) preferenceConstructor.newInstance(params);
    120         } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
    121                 IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
    122             throw new IllegalStateException(
    123                     "Invalid preference controller: " + controllerName, e);
    124         }
    125     }
    126 
    127     /**
    128      * Instantiate a controller as specified controller type.
    129      * <p/>
    130      * This is done through reflection. Do not use this method unless you know what you are doing.
    131      */
    132     public static BasePreferenceController createInstance(Context context, String controllerName) {
    133         try {
    134             final Class<?> clazz = Class.forName(controllerName);
    135             final Constructor<?> preferenceConstructor = clazz.getConstructor(Context.class);
    136             final Object[] params = new Object[] {context};
    137             return (BasePreferenceController) preferenceConstructor.newInstance(params);
    138         } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
    139                 IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
    140             throw new IllegalStateException(
    141                     "Invalid preference controller: " + controllerName, e);
    142         }
    143     }
    144 
    145     public BasePreferenceController(Context context, String preferenceKey) {
    146         super(context);
    147         mPreferenceKey = preferenceKey;
    148         if (TextUtils.isEmpty(mPreferenceKey)) {
    149             throw new IllegalArgumentException("Preference key must be set");
    150         }
    151     }
    152 
    153     /**
    154      * @return {@AvailabilityStatus} for the Setting. This status is used to determine if the
    155      * Setting should be shown or disabled in Settings. Further, it can be used to produce
    156      * appropriate error / warning Slice in the case of unavailability.
    157      * </p>
    158      * The status is used for the convenience methods: {@link #isAvailable()},
    159      * {@link #isSupported()}
    160      */
    161     @AvailabilityStatus
    162     public abstract int getAvailabilityStatus();
    163 
    164     @Override
    165     public String getPreferenceKey() {
    166         return mPreferenceKey;
    167     }
    168 
    169     /**
    170      * @return {@code true} when the controller can be changed on the device.
    171      *
    172      * <p>
    173      * Will return true for {@link #AVAILABLE} and {@link #DISABLED_DEPENDENT_SETTING}.
    174      * <p>
    175      * When the availability status returned by {@link #getAvailabilityStatus()} is
    176      * {@link #DISABLED_DEPENDENT_SETTING}, then the setting will be disabled by default in the
    177      * DashboardFragment, and it is up to the {@link BasePreferenceController} to enable the
    178      * preference at the right time.
    179      *
    180      * TODO (mfritze) Build a dependency mechanism to allow a controller to easily define the
    181      * dependent setting.
    182      */
    183     @Override
    184     public final boolean isAvailable() {
    185         final int availabilityStatus = getAvailabilityStatus();
    186         return (availabilityStatus == AVAILABLE
    187                 || availabilityStatus == DISABLED_DEPENDENT_SETTING);
    188     }
    189 
    190     /**
    191      * @return {@code false} if the setting is not applicable to the device. This covers both
    192      * settings which were only introduced in future versions of android, or settings that have
    193      * hardware dependencies.
    194      * </p>
    195      * Note that a return value of {@code true} does not mean that the setting is available.
    196      */
    197     public final boolean isSupported() {
    198         return getAvailabilityStatus() != UNSUPPORTED_ON_DEVICE;
    199     }
    200 
    201     /**
    202      * Displays preference in this controller.
    203      */
    204     @Override
    205     public void displayPreference(PreferenceScreen screen) {
    206         super.displayPreference(screen);
    207         if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
    208             // Disable preference if it depends on another setting.
    209             final Preference preference = screen.findPreference(getPreferenceKey());
    210             if (preference != null) {
    211                 preference.setEnabled(false);
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * @return the UI type supported by the controller.
    218      */
    219     @SliceData.SliceType
    220     public int getSliceType() {
    221         return SliceData.SliceType.INTENT;
    222     }
    223 
    224     /**
    225      * @return an {@link IntentFilter} that includes all broadcasts which can affect the state of
    226      * this Setting.
    227      */
    228     public IntentFilter getIntentFilter() {
    229         return null;
    230     }
    231 
    232     /**
    233      * Determines if the controller should be used as a Slice.
    234      * <p>
    235      *     Important criteria for a Slice are:
    236      *     - Must be secure
    237      *     - Must not be a privacy leak
    238      *     - Must be understandable as a stand-alone Setting.
    239      * <p>
    240      *     This does not guarantee the setting is available. {@link #isAvailable()} should sill be
    241      *     called.
    242      *
    243      * @return {@code true} if the controller should be used externally as a Slice.
    244      */
    245     public boolean isSliceable() {
    246         return false;
    247     }
    248 
    249     /**
    250      * @return {@code true} if the setting update asynchronously.
    251      * <p>
    252      * For example, a Wifi controller would return true, because it needs to update the radio
    253      * and wait for it to turn on.
    254      */
    255     public boolean hasAsyncUpdate() {
    256         return false;
    257     }
    258 
    259     /**
    260      * Updates non-indexable keys for search provider.
    261      *
    262      * Called by SearchIndexProvider#getNonIndexableKeys
    263      */
    264     public void updateNonIndexableKeys(List<String> keys) {
    265         if (this instanceof AbstractPreferenceController) {
    266             if (!isAvailable()) {
    267                 final String key = getPreferenceKey();
    268                 if (TextUtils.isEmpty(key)) {
    269                     Log.w(TAG,
    270                             "Skipping updateNonIndexableKeys due to empty key " + this.toString());
    271                     return;
    272                 }
    273                 keys.add(key);
    274             }
    275         }
    276     }
    277 
    278     /**
    279      * Updates raw data for search provider.
    280      *
    281      * Called by SearchIndexProvider#getRawDataToIndex
    282      */
    283     public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
    284     }
    285 
    286     /**
    287      * @return the {@link ResultPayload} corresponding to the search result type for the preference.
    288      * TODO (b/69808376) Remove this method.
    289      * Do not extend this method. It will not launch with P.
    290      */
    291     @Deprecated
    292     public ResultPayload getResultPayload() {
    293         return null;
    294     }
    295 }