blob: 45384105cc8fe1133b62e33801ab8f41fa1d2508 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.util.Log;
import android.util.MutableInt;
import com.android.internal.annotations.GuardedBy;
import libcore.util.HexEncoding;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
/**
* Gives access to the system properties store. The system properties
* store contains a list of string key-value pairs.
*
* {@hide}
*/
@SystemApi
@TestApi
public class SystemProperties {
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
/**
* Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5
* uses reflection to read this whenever text is selected (http://b/36095274).
* @hide
*/
@UnsupportedAppUsage
public static final int PROP_NAME_MAX = Integer.MAX_VALUE;
/** @hide */
public static final int PROP_VALUE_MAX = 91;
@UnsupportedAppUsage
@GuardedBy("sChangeCallbacks")
private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
@GuardedBy("sRoReads")
private static final HashMap<String, MutableInt> sRoReads =
TRACK_KEY_ACCESS ? new HashMap<>() : null;
private static void onKeyAccess(String key) {
if (!TRACK_KEY_ACCESS) return;
if (key != null && key.startsWith("ro.")) {
synchronized (sRoReads) {
MutableInt numReads = sRoReads.getOrDefault(key, null);
if (numReads == null) {
numReads = new MutableInt(0);
sRoReads.put(key, numReads);
}
numReads.value++;
if (numReads.value > 3) {
Log.d(TAG, "Repeated read (count=" + numReads.value
+ ") of a read-only system property '" + key + "'",
new Exception());
}
}
}
}
@UnsupportedAppUsage
private static native String native_get(String key);
private static native String native_get(String key, String def);
private static native int native_get_int(String key, int def);
@UnsupportedAppUsage
private static native long native_get_long(String key, long def);
private static native boolean native_get_boolean(String key, boolean def);
private static native void native_set(String key, String def);
private static native void native_add_change_callback();
private static native void native_report_sysprop_change();
/**
* Get the String value for the given {@code key}.
*
* @param key the key to lookup
* @return an empty string if the {@code key} isn't found
* @hide
*/
@NonNull
@SystemApi
@TestApi
public static String get(@NonNull String key) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
/**
* Get the String value for the given {@code key}.
*
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
* string otherwise
* @hide
*/
@NonNull
@SystemApi
@TestApi
public static String get(@NonNull String key, @Nullable String def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
/**
* Get the value for the given {@code key}, and return as an integer.
*
* @param key the key to lookup
* @param def a default value to return
* @return the key parsed as an integer, or def if the key isn't found or
* cannot be parsed
* @hide
*/
@SystemApi
public static int getInt(@NonNull String key, int def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_int(key, def);
}
/**
* Get the value for the given {@code key}, and return as a long.
*
* @param key the key to lookup
* @param def a default value to return
* @return the key parsed as a long, or def if the key isn't found or
* cannot be parsed
* @hide
*/
@SystemApi
public static long getLong(@NonNull String key, long def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_long(key, def);
}
/**
* Get the value for the given {@code key}, returned as a boolean.
* Values 'n', 'no', '0', 'false' or 'off' are considered false.
* Values 'y', 'yes', '1', 'true' or 'on' are considered true.
* (case sensitive).
* If the key does not exist, or has any other value, then the default
* result is returned.
*
* @param key the key to lookup
* @param def a default value to return
* @return the key parsed as a boolean, or def if the key isn't found or is
* not able to be parsed as a boolean.
* @hide
*/
@SystemApi
@TestApi
public static boolean getBoolean(@NonNull String key, boolean def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_boolean(key, def);
}
/**
* Set the value for the given {@code key} to {@code val}.
*
* @throws IllegalArgumentException if the {@code val} exceeds 91 characters
* @hide
*/
@UnsupportedAppUsage
public static void set(@NonNull String key, @Nullable String val) {
if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("value of system property '" + key
+ "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
}
if (TRACK_KEY_ACCESS) onKeyAccess(key);
native_set(key, val);
}
/**
* Add a callback that will be run whenever any system property changes.
*
* @param callback The {@link Runnable} that should be executed when a system property
* changes.
* @hide
*/
@UnsupportedAppUsage
public static void addChangeCallback(@NonNull Runnable callback) {
synchronized (sChangeCallbacks) {
if (sChangeCallbacks.size() == 0) {
native_add_change_callback();
}
sChangeCallbacks.add(callback);
}
}
@SuppressWarnings("unused") // Called from native code.
private static void callChangeCallbacks() {
synchronized (sChangeCallbacks) {
//Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");
if (sChangeCallbacks.size() == 0) {
return;
}
ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks);
final long token = Binder.clearCallingIdentity();
try {
for (int i = 0; i < callbacks.size(); i++) {
try {
callbacks.get(i).run();
} catch (Throwable t) {
Log.wtf(TAG, "Exception in SystemProperties change callback", t);
// Ignore and try to go on.
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* Notifies listeners that a system property has changed
* @hide
*/
@UnsupportedAppUsage
public static void reportSyspropChanged() {
native_report_sysprop_change();
}
/**
* Return a {@code SHA-1} digest of the given keys and their values as a
* hex-encoded string. The ordering of the incoming keys doesn't change the
* digest result.
*
* @hide
*/
public static @NonNull String digestOf(@NonNull String... keys) {
Arrays.sort(keys);
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
for (String key : keys) {
final String item = key + "=" + get(key) + "\n";
digest.update(item.getBytes(StandardCharsets.UTF_8));
}
return HexEncoding.encodeToString(digest.digest()).toLowerCase();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@UnsupportedAppUsage
private SystemProperties() {
}
}