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 */ 14package com.android.settings.core; 15 16import android.annotation.IntDef; 17import android.content.Context; 18import android.content.IntentFilter; 19import android.text.TextUtils; 20import android.util.Log; 21 22import com.android.settings.search.ResultPayload; 23import com.android.settings.search.SearchIndexableRaw; 24import com.android.settings.slices.SliceData; 25import com.android.settingslib.core.AbstractPreferenceController; 26 27import java.lang.annotation.Retention; 28import java.lang.annotation.RetentionPolicy; 29import java.lang.reflect.Constructor; 30import java.lang.reflect.InvocationTargetException; 31import java.util.List; 32 33import android.support.v7.preference.Preference; 34import android.support.v7.preference.PreferenceGroup; 35import 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 */ 42public 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}