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.provider.settings.validators; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.net.Uri; 23 import android.text.TextUtils; 24 25 import org.json.JSONException; 26 import org.json.JSONObject; 27 28 import java.util.Locale; 29 30 /** 31 * This class provides both interface for validation and common validators 32 * used to ensure Settings have meaningful values. 33 * 34 * @hide 35 */ 36 public class SettingsValidators { 37 38 public static final Validator BOOLEAN_VALIDATOR = 39 new DiscreteValueValidator(new String[] {"0", "1"}); 40 41 public static final Validator ANY_STRING_VALIDATOR = new Validator() { 42 @Override 43 public boolean validate(@Nullable String value) { 44 return true; 45 } 46 }; 47 48 public static final Validator FONT_SCALE_VALIDATOR = new InclusiveFloatRangeValidator(0.25f, 49 5.0f); 50 51 public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() { 52 @Override 53 public boolean validate(@Nullable String value) { 54 if (value == null) { 55 return true; 56 } 57 try { 58 return Integer.parseInt(value) >= 0; 59 } catch (NumberFormatException e) { 60 return false; 61 } 62 } 63 }; 64 65 public static final Validator ANY_INTEGER_VALIDATOR = new Validator() { 66 @Override 67 public boolean validate(@Nullable String value) { 68 if (value == null) { 69 return true; 70 } 71 try { 72 Integer.parseInt(value); 73 return true; 74 } catch (NumberFormatException e) { 75 return false; 76 } 77 } 78 }; 79 80 public static final Validator NON_NEGATIVE_FLOAT_VALIDATOR = new Validator() { 81 @Override 82 public boolean validate(@Nullable String value) { 83 if (value == null) { 84 return true; 85 } 86 try { 87 return Float.parseFloat(value) >= 0.0f; 88 } catch (NumberFormatException e) { 89 return false; 90 } 91 } 92 }; 93 94 public static final Validator URI_VALIDATOR = new Validator() { 95 @Override 96 public boolean validate(@Nullable String value) { 97 if (value == null) { 98 return true; 99 } 100 try { 101 Uri.decode(value); 102 return true; 103 } catch (IllegalArgumentException e) { 104 return false; 105 } 106 } 107 }; 108 109 /** 110 * Does not allow a setting to have a null {@link ComponentName}. Use {@link 111 * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a 112 * nullable {@link ComponentName}. 113 */ 114 public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() { 115 @Override 116 public boolean validate(@NonNull String value) { 117 return value != null && ComponentName.unflattenFromString(value) != null; 118 } 119 }; 120 121 /** 122 * Allows a setting to have a null {@link ComponentName}. 123 */ 124 public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() { 125 @Override 126 public boolean validate(@Nullable String value) { 127 return value == null || COMPONENT_NAME_VALIDATOR.validate(value); 128 } 129 }; 130 131 public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() { 132 @Override 133 public boolean validate(@NonNull String value) { 134 return value != null && isStringPackageName(value); 135 } 136 137 private boolean isStringPackageName(@NonNull String value) { 138 // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers, 139 // and underscores ('_'). However, individual package name parts may only 140 // start with letters. 141 // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package) 142 String[] subparts = value.split("\\."); 143 boolean isValidPackageName = true; 144 for (String subpart : subparts) { 145 isValidPackageName &= isSubpartValidForPackageName(subpart); 146 if (!isValidPackageName) break; 147 } 148 return isValidPackageName; 149 } 150 151 private boolean isSubpartValidForPackageName(String subpart) { 152 if (subpart.length() == 0) return false; 153 boolean isValidSubpart = Character.isLetter(subpart.charAt(0)); 154 for (int i = 1; i < subpart.length(); i++) { 155 isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i)) 156 || (subpart.charAt(i) == '_')); 157 if (!isValidSubpart) break; 158 } 159 return isValidSubpart; 160 } 161 }; 162 163 public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() { 164 private static final int MAX_IPV6_LENGTH = 45; 165 166 @Override 167 public boolean validate(@Nullable String value) { 168 if (value == null) { 169 return true; 170 } 171 return value.length() <= MAX_IPV6_LENGTH; 172 } 173 }; 174 175 public static final Validator LOCALE_VALIDATOR = new Validator() { 176 @Override 177 public boolean validate(@Nullable String value) { 178 if (value == null) { 179 return true; 180 } 181 Locale[] validLocales = Locale.getAvailableLocales(); 182 for (Locale locale : validLocales) { 183 if (value.equals(locale.toString())) { 184 return true; 185 } 186 } 187 return false; 188 } 189 }; 190 191 /** 192 * Similar to {@link #LOCALE_VALIDATOR} but allows loose/subset matches against the list 193 * from {@link Locale#getAvailableLocales()}. 194 * 195 * <p>Expects that the string is '_'-separated with 1 to 3 parts. Then checks the parts against 196 * the locale list, returning true if any available locale matches the parts provided using 197 * case insensitive string comparison. 198 * <li>Part 1: match against {@link Locale#getLanguage()}</li> 199 * <li>Part 2, if present: match against {@link Locale#getCountry()}</li> 200 * <li>Part 3, if present: match against {@link Locale#getVariant()}</li> 201 */ 202 public static final Validator LOCALE_LOOSE_VALIDATOR = value -> { 203 if (value == null) { 204 return true; 205 } 206 String[] parts = value.split("_", 3); 207 Locale[] validLocales = Locale.getAvailableLocales(); 208 for (Locale locale : validLocales) { 209 if (!parts[0].equalsIgnoreCase(locale.getLanguage())) { 210 continue; 211 } 212 if (parts.length >= 2 && !parts[1].equalsIgnoreCase(locale.getCountry())) { 213 continue; 214 } 215 if (parts.length == 3 && !parts[2].equalsIgnoreCase(locale.getVariant())) { 216 continue; 217 } 218 return true; 219 } 220 return false; 221 }; 222 223 /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */ 224 public static final Validator JSON_OBJECT_VALIDATOR = (value) -> { 225 if (value == null) { 226 return true; 227 } 228 if (TextUtils.isEmpty(value)) { 229 return false; 230 } 231 try { 232 new JSONObject(value); 233 return true; 234 } catch (JSONException e) { 235 return false; 236 } 237 }; 238 239 public static final Validator TTS_LIST_VALIDATOR = new TTSListValidator(); 240 241 public static final Validator TILE_LIST_VALIDATOR = new TileListValidator(); 242 243 static final Validator COLON_SEPARATED_COMPONENT_LIST_VALIDATOR = 244 new ComponentNameListValidator(":"); 245 246 static final Validator COLON_SEPARATED_PACKAGE_LIST_VALIDATOR = 247 new PackageNameListValidator(":"); 248 249 static final Validator COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR = 250 new ComponentNameListValidator(","); 251 252 static final Validator PERCENTAGE_INTEGER_VALIDATOR = 253 new InclusiveIntegerRangeValidator(0, 100); 254 255 static final Validator VIBRATION_INTENSITY_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3); 256 257 static final Validator ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR = 258 new AccessibilityShortcutTargetListValidator(); 259 260 static final Validator NONE_NEGATIVE_LONG_VALIDATOR = new Validator() { 261 @Override 262 public boolean validate(String value) { 263 if (value == null) { 264 return true; 265 } 266 try { 267 return Long.parseLong(value) >= 0; 268 } catch (NumberFormatException e) { 269 return false; 270 } 271 } 272 }; 273 274 static final Validator ANY_LONG_VALIDATOR = value -> { 275 if (value == null) { 276 return true; 277 } 278 try { 279 Long.parseLong(value); 280 return true; 281 } catch (NumberFormatException e) { 282 return false; 283 } 284 }; 285 286 static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() { 287 @Override 288 public boolean validate(String value) { 289 if (value == null || value.equals("")) { 290 return true; 291 } 292 293 return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value); 294 } 295 }; 296 297 static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() { 298 @Override 299 public boolean validate(String value) { 300 if (value == null || value.equals("")) { 301 return true; 302 } 303 304 if (value.equals("credential-provider")) { 305 return true; 306 } 307 308 return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value); 309 } 310 }; 311 } 312