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