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