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