1 /*
2  * Copyright (C) 2006 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.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.annotation.UnsupportedAppUsage;
24 import android.util.Log;
25 import android.util.MutableInt;
26 
27 import com.android.internal.annotations.GuardedBy;
28 
29 import libcore.util.HexEncoding;
30 
31 import java.nio.charset.StandardCharsets;
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 
38 /**
39  * Gives access to the system properties store.  The system properties
40  * store contains a list of string key-value pairs.
41  *
42  * {@hide}
43  */
44 @SystemApi
45 @TestApi
46 public class SystemProperties {
47     private static final String TAG = "SystemProperties";
48     private static final boolean TRACK_KEY_ACCESS = false;
49 
50     /**
51      * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5
52      * uses reflection to read this whenever text is selected (http://b/36095274).
53      * @hide
54      */
55     @UnsupportedAppUsage
56     public static final int PROP_NAME_MAX = Integer.MAX_VALUE;
57 
58     /** @hide */
59     public static final int PROP_VALUE_MAX = 91;
60 
61     @UnsupportedAppUsage
62     @GuardedBy("sChangeCallbacks")
63     private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
64 
65     @GuardedBy("sRoReads")
66     private static final HashMap<String, MutableInt> sRoReads =
67             TRACK_KEY_ACCESS ? new HashMap<>() : null;
68 
onKeyAccess(String key)69     private static void onKeyAccess(String key) {
70         if (!TRACK_KEY_ACCESS) return;
71 
72         if (key != null && key.startsWith("ro.")) {
73             synchronized (sRoReads) {
74                 MutableInt numReads = sRoReads.getOrDefault(key, null);
75                 if (numReads == null) {
76                     numReads = new MutableInt(0);
77                     sRoReads.put(key, numReads);
78                 }
79                 numReads.value++;
80                 if (numReads.value > 3) {
81                     Log.d(TAG, "Repeated read (count=" + numReads.value
82                             + ") of a read-only system property '" + key + "'",
83                             new Exception());
84                 }
85             }
86         }
87     }
88 
89     @UnsupportedAppUsage
native_get(String key)90     private static native String native_get(String key);
native_get(String key, String def)91     private static native String native_get(String key, String def);
native_get_int(String key, int def)92     private static native int native_get_int(String key, int def);
93     @UnsupportedAppUsage
native_get_long(String key, long def)94     private static native long native_get_long(String key, long def);
native_get_boolean(String key, boolean def)95     private static native boolean native_get_boolean(String key, boolean def);
native_set(String key, String def)96     private static native void native_set(String key, String def);
native_add_change_callback()97     private static native void native_add_change_callback();
native_report_sysprop_change()98     private static native void native_report_sysprop_change();
99 
100     /**
101      * Get the String value for the given {@code key}.
102      *
103      * @param key the key to lookup
104      * @return an empty string if the {@code key} isn't found
105      * @hide
106      */
107     @NonNull
108     @SystemApi
109     @TestApi
get(@onNull String key)110     public static String get(@NonNull String key) {
111         if (TRACK_KEY_ACCESS) onKeyAccess(key);
112         return native_get(key);
113     }
114 
115     /**
116      * Get the String value for the given {@code key}.
117      *
118      * @param key the key to lookup
119      * @param def the default value in case the property is not set or empty
120      * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
121      * string otherwise
122      * @hide
123      */
124     @NonNull
125     @SystemApi
126     @TestApi
get(@onNull String key, @Nullable String def)127     public static String get(@NonNull String key, @Nullable String def) {
128         if (TRACK_KEY_ACCESS) onKeyAccess(key);
129         return native_get(key, def);
130     }
131 
132     /**
133      * Get the value for the given {@code key}, and return as an integer.
134      *
135      * @param key the key to lookup
136      * @param def a default value to return
137      * @return the key parsed as an integer, or def if the key isn't found or
138      *         cannot be parsed
139      * @hide
140      */
141     @SystemApi
getInt(@onNull String key, int def)142     public static int getInt(@NonNull String key, int def) {
143         if (TRACK_KEY_ACCESS) onKeyAccess(key);
144         return native_get_int(key, def);
145     }
146 
147     /**
148      * Get the value for the given {@code key}, and return as a long.
149      *
150      * @param key the key to lookup
151      * @param def a default value to return
152      * @return the key parsed as a long, or def if the key isn't found or
153      *         cannot be parsed
154      * @hide
155      */
156     @SystemApi
getLong(@onNull String key, long def)157     public static long getLong(@NonNull String key, long def) {
158         if (TRACK_KEY_ACCESS) onKeyAccess(key);
159         return native_get_long(key, def);
160     }
161 
162     /**
163      * Get the value for the given {@code key}, returned as a boolean.
164      * Values 'n', 'no', '0', 'false' or 'off' are considered false.
165      * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
166      * (case sensitive).
167      * If the key does not exist, or has any other value, then the default
168      * result is returned.
169      *
170      * @param key the key to lookup
171      * @param def a default value to return
172      * @return the key parsed as a boolean, or def if the key isn't found or is
173      *         not able to be parsed as a boolean.
174      * @hide
175      */
176     @SystemApi
177     @TestApi
getBoolean(@onNull String key, boolean def)178     public static boolean getBoolean(@NonNull String key, boolean def) {
179         if (TRACK_KEY_ACCESS) onKeyAccess(key);
180         return native_get_boolean(key, def);
181     }
182 
183     /**
184      * Set the value for the given {@code key} to {@code val}.
185      *
186      * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
187      * @hide
188      */
189     @UnsupportedAppUsage
set(@onNull String key, @Nullable String val)190     public static void set(@NonNull String key, @Nullable String val) {
191         if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
192             throw new IllegalArgumentException("value of system property '" + key
193                     + "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
194         }
195         if (TRACK_KEY_ACCESS) onKeyAccess(key);
196         native_set(key, val);
197     }
198 
199     /**
200      * Add a callback that will be run whenever any system property changes.
201      *
202      * @param callback The {@link Runnable} that should be executed when a system property
203      * changes.
204      * @hide
205      */
206     @UnsupportedAppUsage
addChangeCallback(@onNull Runnable callback)207     public static void addChangeCallback(@NonNull Runnable callback) {
208         synchronized (sChangeCallbacks) {
209             if (sChangeCallbacks.size() == 0) {
210                 native_add_change_callback();
211             }
212             sChangeCallbacks.add(callback);
213         }
214     }
215 
216     @SuppressWarnings("unused")  // Called from native code.
callChangeCallbacks()217     private static void callChangeCallbacks() {
218         synchronized (sChangeCallbacks) {
219             //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");
220             if (sChangeCallbacks.size() == 0) {
221                 return;
222             }
223             ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks);
224             final long token = Binder.clearCallingIdentity();
225             try {
226                 for (int i = 0; i < callbacks.size(); i++) {
227                     try {
228                         callbacks.get(i).run();
229                     } catch (Throwable t) {
230                         Log.wtf(TAG, "Exception in SystemProperties change callback", t);
231                         // Ignore and try to go on.
232                     }
233                 }
234             } finally {
235                 Binder.restoreCallingIdentity(token);
236             }
237         }
238     }
239 
240     /**
241      * Notifies listeners that a system property has changed
242      * @hide
243      */
244     @UnsupportedAppUsage
reportSyspropChanged()245     public static void reportSyspropChanged() {
246         native_report_sysprop_change();
247     }
248 
249     /**
250      * Return a {@code SHA-1} digest of the given keys and their values as a
251      * hex-encoded string. The ordering of the incoming keys doesn't change the
252      * digest result.
253      *
254      * @hide
255      */
digestOf(@onNull String... keys)256     public static @NonNull String digestOf(@NonNull String... keys) {
257         Arrays.sort(keys);
258         try {
259             final MessageDigest digest = MessageDigest.getInstance("SHA-1");
260             for (String key : keys) {
261                 final String item = key + "=" + get(key) + "\n";
262                 digest.update(item.getBytes(StandardCharsets.UTF_8));
263             }
264             return HexEncoding.encodeToString(digest.digest()).toLowerCase();
265         } catch (NoSuchAlgorithmException e) {
266             throw new RuntimeException(e);
267         }
268     }
269 
270     @UnsupportedAppUsage
SystemProperties()271     private SystemProperties() {
272     }
273 }
274