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