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 package android.view.textclassifier;
17 
18 import android.annotation.Nullable;
19 import android.provider.DeviceConfig;
20 import android.util.ArrayMap;
21 import android.util.KeyValueListParser;
22 
23 import com.android.internal.annotations.GuardedBy;
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.annotations.VisibleForTesting.Visibility;
26 import com.android.internal.util.Preconditions;
27 
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.function.Supplier;
33 
34 /**
35  * Retrieves settings from {@link DeviceConfig} and {@link android.provider.Settings}.
36  * It will try DeviceConfig first and then Settings.
37  *
38  * @hide
39  */
40 @VisibleForTesting(visibility = Visibility.PACKAGE)
41 public final class ConfigParser {
42     private static final String TAG = "ConfigParser";
43 
44     static final boolean ENABLE_DEVICE_CONFIG = true;
45 
46     private static final String STRING_LIST_DELIMITER = ":";
47 
48     private final Supplier<String> mLegacySettingsSupplier;
49     private final Object mLock = new Object();
50     @GuardedBy("mLock")
51     private final Map<String, Object> mCache = new ArrayMap<>();
52     @GuardedBy("mLock")
53     private @Nullable KeyValueListParser mSettingsParser;  // Call getLegacySettings() instead.
54 
ConfigParser(Supplier<String> legacySettingsSupplier)55     public ConfigParser(Supplier<String> legacySettingsSupplier) {
56         mLegacySettingsSupplier = Preconditions.checkNotNull(legacySettingsSupplier);
57     }
58 
getLegacySettings()59     private KeyValueListParser getLegacySettings() {
60         synchronized (mLock) {
61             if (mSettingsParser == null) {
62                 final String legacySettings = mLegacySettingsSupplier.get();
63                 try {
64                     mSettingsParser = new KeyValueListParser(',');
65                     mSettingsParser.setString(legacySettings);
66                 } catch (IllegalArgumentException e) {
67                     // Failed to parse the settings string, log this and move on with defaults.
68                     Log.w(TAG, "Bad text_classifier_constants: " + legacySettings);
69                 }
70             }
71             return mSettingsParser;
72         }
73     }
74 
75     /**
76      * Reads a boolean setting through the cache.
77      */
getBoolean(String key, boolean defaultValue)78     public boolean getBoolean(String key, boolean defaultValue) {
79         synchronized (mLock) {
80             final Object cached = mCache.get(key);
81             if (cached instanceof Boolean) {
82                 return (boolean) cached;
83             }
84             final boolean value;
85             if (ENABLE_DEVICE_CONFIG) {
86                 value = DeviceConfig.getBoolean(
87                         DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
88                         key,
89                         getLegacySettings().getBoolean(key, defaultValue));
90             } else {
91                 value = getLegacySettings().getBoolean(key, defaultValue);
92             }
93             mCache.put(key, value);
94             return value;
95         }
96     }
97 
98     /**
99      * Reads an integer setting through the cache.
100      */
getInt(String key, int defaultValue)101     public int getInt(String key, int defaultValue) {
102         synchronized (mLock) {
103             final Object cached = mCache.get(key);
104             if (cached instanceof Integer) {
105                 return (int) cached;
106             }
107             final int value;
108             if (ENABLE_DEVICE_CONFIG) {
109                 value = DeviceConfig.getInt(
110                         DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
111                         key,
112                         getLegacySettings().getInt(key, defaultValue));
113             } else {
114                 value = getLegacySettings().getInt(key, defaultValue);
115             }
116             mCache.put(key, value);
117             return value;
118         }
119     }
120 
121     /**
122      * Reads a float setting through the cache.
123      */
getFloat(String key, float defaultValue)124     public float getFloat(String key, float defaultValue) {
125         synchronized (mLock) {
126             final Object cached = mCache.get(key);
127             if (cached instanceof Float) {
128                 return (float) cached;
129             }
130             final float value;
131             if (ENABLE_DEVICE_CONFIG) {
132                 value = DeviceConfig.getFloat(
133                         DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
134                         key,
135                         getLegacySettings().getFloat(key, defaultValue));
136             } else {
137                 value = getLegacySettings().getFloat(key, defaultValue);
138             }
139             mCache.put(key, value);
140             return value;
141         }
142     }
143 
144     /**
145      * Reads a string setting through the cache.
146      */
getString(String key, String defaultValue)147     public String getString(String key, String defaultValue) {
148         synchronized (mLock) {
149             final Object cached = mCache.get(key);
150             if (cached instanceof String) {
151                 return (String) cached;
152             }
153             final String value;
154             if (ENABLE_DEVICE_CONFIG) {
155                 value = DeviceConfig.getString(
156                         DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
157                         key,
158                         getLegacySettings().getString(key, defaultValue));
159             } else {
160                 value = getLegacySettings().getString(key, defaultValue);
161             }
162             mCache.put(key, value);
163             return value;
164         }
165     }
166 
167     /**
168      * Reads a string list setting through the cache.
169      */
getStringList(String key, List<String> defaultValue)170     public List<String> getStringList(String key, List<String> defaultValue) {
171         synchronized (mLock) {
172             final Object cached = mCache.get(key);
173             if (cached instanceof List) {
174                 final List asList = (List) cached;
175                 if (asList.isEmpty()) {
176                     return Collections.emptyList();
177                 } else if (asList.get(0) instanceof String) {
178                     return (List<String>) cached;
179                 }
180             }
181             final List<String> value;
182             if (ENABLE_DEVICE_CONFIG) {
183                 value = getDeviceConfigStringList(
184                         key,
185                         getSettingsStringList(key, defaultValue));
186             } else {
187                 value = getSettingsStringList(key, defaultValue);
188             }
189             mCache.put(key, value);
190             return value;
191         }
192     }
193 
194     /**
195      * Reads a float array through the cache. The returned array should be expected to be of the
196      * same length as that of the defaultValue.
197      */
getFloatArray(String key, float[] defaultValue)198     public float[] getFloatArray(String key, float[] defaultValue) {
199         synchronized (mLock) {
200             final Object cached = mCache.get(key);
201             if (cached instanceof float[]) {
202                 return (float[]) cached;
203             }
204             final float[] value;
205             if (ENABLE_DEVICE_CONFIG) {
206                 value = getDeviceConfigFloatArray(
207                         key,
208                         getSettingsFloatArray(key, defaultValue));
209             } else {
210                 value = getSettingsFloatArray(key, defaultValue);
211             }
212             mCache.put(key, value);
213             return value;
214         }
215     }
216 
getSettingsStringList(String key, List<String> defaultValue)217     private List<String> getSettingsStringList(String key, List<String> defaultValue) {
218         return parse(mSettingsParser.getString(key, null), defaultValue);
219     }
220 
getDeviceConfigStringList(String key, List<String> defaultValue)221     private static List<String> getDeviceConfigStringList(String key, List<String> defaultValue) {
222         return parse(
223                 DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
224                 defaultValue);
225     }
226 
getDeviceConfigFloatArray(String key, float[] defaultValue)227     private static float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
228         return parse(
229                 DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
230                 defaultValue);
231     }
232 
getSettingsFloatArray(String key, float[] defaultValue)233     private float[] getSettingsFloatArray(String key, float[] defaultValue) {
234         return parse(mSettingsParser.getString(key, null), defaultValue);
235     }
236 
parse(@ullable String listStr, List<String> defaultValue)237     private static List<String> parse(@Nullable String listStr, List<String> defaultValue) {
238         if (listStr != null) {
239             return Collections.unmodifiableList(
240                     Arrays.asList(listStr.split(STRING_LIST_DELIMITER)));
241         }
242         return defaultValue;
243     }
244 
parse(@ullable String arrayStr, float[] defaultValue)245     private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
246         if (arrayStr != null) {
247             final String[] split = arrayStr.split(STRING_LIST_DELIMITER);
248             if (split.length != defaultValue.length) {
249                 return defaultValue;
250             }
251             final float[] result = new float[split.length];
252             for (int i = 0; i < split.length; i++) {
253                 try {
254                     result[i] = Float.parseFloat(split[i]);
255                 } catch (NumberFormatException e) {
256                     return defaultValue;
257                 }
258             }
259             return result;
260         } else {
261             return defaultValue;
262         }
263     }
264 }
265