OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / packages / SettingsProvider / src / com / android / providers / settings / SettingsState.java
1 /*
2  * Copyright (C) 2015 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 com.android.providers.settings;
18
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.os.Message;
22 import android.os.SystemClock;
23 import android.provider.Settings;
24 import android.text.TextUtils;
25 import android.util.ArrayMap;
26 import android.util.AtomicFile;
27 import android.util.Base64;
28 import android.util.Slog;
29 import android.util.Xml;
30 import com.android.internal.annotations.GuardedBy;
31 import libcore.io.IoUtils;
32 import libcore.util.Objects;
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
36
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileNotFoundException;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.nio.charset.StandardCharsets;
43 import java.util.ArrayList;
44 import java.util.List;
45
46 /**
47  * This class contains the state for one type of settings. It is responsible
48  * for saving the state asynchronously to an XML file after a mutation and
49  * loading the from an XML file on construction.
50  * <p>
51  * This class uses the same lock as the settings provider to ensure that
52  * multiple changes made by the settings provider, e,g, upgrade, bulk insert,
53  * etc, are atomically persisted since the asynchronous persistence is using
54  * the same lock to grab the current state to write to disk.
55  * </p>
56  */
57 final class SettingsState {
58     private static final boolean DEBUG = false;
59     private static final boolean DEBUG_PERSISTENCE = false;
60
61     private static final String LOG_TAG = "SettingsState";
62
63     static final int SETTINGS_VERSOIN_NEW_ENCODING = 121;
64
65     private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
66     private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
67
68     public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
69     public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
70
71     public static final String SYSTEM_PACKAGE_NAME = "android";
72
73     public static final int VERSION_UNDEFINED = -1;
74
75     private static final String TAG_SETTINGS = "settings";
76     private static final String TAG_SETTING = "setting";
77     private static final String ATTR_PACKAGE = "package";
78
79     private static final String ATTR_VERSION = "version";
80     private static final String ATTR_ID = "id";
81     private static final String ATTR_NAME = "name";
82
83     /** Non-binary value will be written in this attribute. */
84     private static final String ATTR_VALUE = "value";
85
86     /**
87      * KXmlSerializer won't like some characters.  We encode such characters in base64 and
88      * store in this attribute.
89      * NOTE: A null value will have NEITHER ATTR_VALUE nor ATTR_VALUE_BASE64.
90      */
91     private static final String ATTR_VALUE_BASE64 = "valueBase64";
92
93     // This was used in version 120 and before.
94     private static final String NULL_VALUE_OLD_STYLE = "null";
95
96     private final Object mLock;
97
98     private final Handler mHandler;
99
100     @GuardedBy("mLock")
101     private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
102
103     @GuardedBy("mLock")
104     private final ArrayMap<String, Integer> mPackageToMemoryUsage;
105
106     @GuardedBy("mLock")
107     private final int mMaxBytesPerAppPackage;
108
109     @GuardedBy("mLock")
110     private final File mStatePersistFile;
111
112     private final Setting mNullSetting = new Setting(null, null, null) {
113         @Override
114         public boolean isNull() {
115             return true;
116         }
117     };
118
119     @GuardedBy("mLock")
120     public final int mKey;
121
122     @GuardedBy("mLock")
123     private int mVersion = VERSION_UNDEFINED;
124
125     @GuardedBy("mLock")
126     private long mLastNotWrittenMutationTimeMillis;
127
128     @GuardedBy("mLock")
129     private boolean mDirty;
130
131     @GuardedBy("mLock")
132     private boolean mWriteScheduled;
133
134     @GuardedBy("mLock")
135     private long mNextId;
136
137     public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
138             Looper looper) {
139         // It is important that we use the same lock as the settings provider
140         // to ensure multiple mutations on this state are atomicaly persisted
141         // as the async persistence should be blocked while we make changes.
142         mLock = lock;
143         mStatePersistFile = file;
144         mKey = key;
145         mHandler = new MyHandler(looper);
146         if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
147             mMaxBytesPerAppPackage = maxBytesPerAppPackage;
148             mPackageToMemoryUsage = new ArrayMap<>();
149         } else {
150             mMaxBytesPerAppPackage = maxBytesPerAppPackage;
151             mPackageToMemoryUsage = null;
152         }
153         synchronized (mLock) {
154             readStateSyncLocked();
155         }
156     }
157
158     // The settings provider must hold its lock when calling here.
159     public int getVersionLocked() {
160         return mVersion;
161     }
162
163     public Setting getNullSetting() {
164         return mNullSetting;
165     }
166
167     // The settings provider must hold its lock when calling here.
168     public void setVersionLocked(int version) {
169         if (version == mVersion) {
170             return;
171         }
172         mVersion = version;
173
174         scheduleWriteIfNeededLocked();
175     }
176
177     // The settings provider must hold its lock when calling here.
178     public void onPackageRemovedLocked(String packageName) {
179         boolean removedSomething = false;
180
181         final int settingCount = mSettings.size();
182         for (int i = settingCount - 1; i >= 0; i--) {
183             String name = mSettings.keyAt(i);
184             // Settings defined by us are never dropped.
185             if (Settings.System.PUBLIC_SETTINGS.contains(name)
186                     || Settings.System.PRIVATE_SETTINGS.contains(name)) {
187                 continue;
188             }
189             Setting setting = mSettings.valueAt(i);
190             if (packageName.equals(setting.packageName)) {
191                 mSettings.removeAt(i);
192                 removedSomething = true;
193             }
194         }
195
196         if (removedSomething) {
197             scheduleWriteIfNeededLocked();
198         }
199     }
200
201     // The settings provider must hold its lock when calling here.
202     public List<String> getSettingNamesLocked() {
203         ArrayList<String> names = new ArrayList<>();
204         final int settingsCount = mSettings.size();
205         for (int i = 0; i < settingsCount; i++) {
206             String name = mSettings.keyAt(i);
207             names.add(name);
208         }
209         return names;
210     }
211
212     // The settings provider must hold its lock when calling here.
213     public Setting getSettingLocked(String name) {
214         if (TextUtils.isEmpty(name)) {
215             return mNullSetting;
216         }
217         Setting setting = mSettings.get(name);
218         if (setting != null) {
219             return new Setting(setting);
220         }
221         return mNullSetting;
222     }
223
224     // The settings provider must hold its lock when calling here.
225     public boolean updateSettingLocked(String name, String value, String packageName) {
226         if (!hasSettingLocked(name)) {
227             return false;
228         }
229
230         return insertSettingLocked(name, value, packageName);
231     }
232
233     // The settings provider must hold its lock when calling here.
234     public boolean insertSettingLocked(String name, String value, String packageName) {
235         if (TextUtils.isEmpty(name)) {
236             return false;
237         }
238
239         Setting oldState = mSettings.get(name);
240         String oldValue = (oldState != null) ? oldState.value : null;
241
242         if (oldState != null) {
243             if (!oldState.update(value, packageName)) {
244                 return false;
245             }
246         } else {
247             Setting state = new Setting(name, value, packageName);
248             mSettings.put(name, state);
249         }
250
251         updateMemoryUsagePerPackageLocked(packageName, oldValue, value);
252
253         scheduleWriteIfNeededLocked();
254
255         return true;
256     }
257
258     // The settings provider must hold its lock when calling here.
259     public void persistSyncLocked() {
260         mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
261         doWriteState();
262     }
263
264     // The settings provider must hold its lock when calling here.
265     public boolean deleteSettingLocked(String name) {
266         if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
267             return false;
268         }
269
270         Setting oldState = mSettings.remove(name);
271
272         updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null);
273
274         scheduleWriteIfNeededLocked();
275
276         return true;
277     }
278
279     // The settings provider must hold its lock when calling here.
280     public void destroyLocked(Runnable callback) {
281         mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
282         if (callback != null) {
283             if (mDirty) {
284                 // Do it without a delay.
285                 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS,
286                         callback).sendToTarget();
287                 return;
288             }
289             callback.run();
290         }
291     }
292
293     private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
294             String newValue) {
295         if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
296             return;
297         }
298
299         if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
300             return;
301         }
302
303         final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
304         final int newValueSize = (newValue != null) ? newValue.length() : 0;
305         final int deltaSize = newValueSize - oldValueSize;
306
307         Integer currentSize = mPackageToMemoryUsage.get(packageName);
308         final int newSize = Math.max((currentSize != null)
309                 ? currentSize + deltaSize : deltaSize, 0);
310
311         if (newSize > mMaxBytesPerAppPackage) {
312             throw new IllegalStateException("You are adding too many system settings. "
313                     + "You should stop using system settings for app specific data"
314                     + " package: " + packageName);
315         }
316
317         if (DEBUG) {
318             Slog.i(LOG_TAG, "Settings for package: " + packageName
319                     + " size: " + newSize + " bytes.");
320         }
321
322         mPackageToMemoryUsage.put(packageName, newSize);
323     }
324
325     private boolean hasSettingLocked(String name) {
326         return mSettings.indexOfKey(name) >= 0;
327     }
328
329     private void scheduleWriteIfNeededLocked() {
330         // If dirty then we have a write already scheduled.
331         if (!mDirty) {
332             mDirty = true;
333             writeStateAsyncLocked();
334         }
335     }
336
337     private void writeStateAsyncLocked() {
338         final long currentTimeMillis = SystemClock.uptimeMillis();
339
340         if (mWriteScheduled) {
341             mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
342
343             // If enough time passed, write without holding off anymore.
344             final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
345                     - mLastNotWrittenMutationTimeMillis;
346             if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_SETTINGS_DELAY_MILLIS) {
347                 mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
348                 return;
349             }
350
351             // Hold off a bit more as settings are frequently changing.
352             final long maxDelayMillis = Math.max(mLastNotWrittenMutationTimeMillis
353                     + MAX_WRITE_SETTINGS_DELAY_MILLIS - currentTimeMillis, 0);
354             final long writeDelayMillis = Math.min(WRITE_SETTINGS_DELAY_MILLIS, maxDelayMillis);
355
356             Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
357             mHandler.sendMessageDelayed(message, writeDelayMillis);
358         } else {
359             mLastNotWrittenMutationTimeMillis = currentTimeMillis;
360             Message message = mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS);
361             mHandler.sendMessageDelayed(message, WRITE_SETTINGS_DELAY_MILLIS);
362             mWriteScheduled = true;
363         }
364     }
365
366     private void doWriteState() {
367         if (DEBUG_PERSISTENCE) {
368             Slog.i(LOG_TAG, "[PERSIST START]");
369         }
370
371         AtomicFile destination = new AtomicFile(mStatePersistFile);
372
373         final int version;
374         final ArrayMap<String, Setting> settings;
375
376         synchronized (mLock) {
377             version = mVersion;
378             settings = new ArrayMap<>(mSettings);
379             mDirty = false;
380             mWriteScheduled = false;
381         }
382
383         FileOutputStream out = null;
384         try {
385             out = destination.startWrite();
386
387             XmlSerializer serializer = Xml.newSerializer();
388             serializer.setOutput(out, StandardCharsets.UTF_8.name());
389             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
390             serializer.startDocument(null, true);
391             serializer.startTag(null, TAG_SETTINGS);
392             serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
393
394             final int settingCount = settings.size();
395             for (int i = 0; i < settingCount; i++) {
396                 Setting setting = settings.valueAt(i);
397
398                 writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
399                         setting.getValue(), setting.getPackageName());
400
401                 if (DEBUG_PERSISTENCE) {
402                     Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
403                 }
404             }
405
406             serializer.endTag(null, TAG_SETTINGS);
407             serializer.endDocument();
408             destination.finishWrite(out);
409
410             if (DEBUG_PERSISTENCE) {
411                 Slog.i(LOG_TAG, "[PERSIST END]");
412             }
413         } catch (Throwable t) {
414             Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
415             destination.failWrite(out);
416         } finally {
417             IoUtils.closeQuietly(out);
418         }
419     }
420
421     static void writeSingleSetting(int version, XmlSerializer serializer, String id,
422             String name, String value, String packageName) throws IOException {
423         if (id == null || isBinary(id) || name == null || isBinary(name)
424                 || packageName == null || isBinary(packageName)) {
425             // This shouldn't happen.
426             return;
427         }
428         serializer.startTag(null, TAG_SETTING);
429         serializer.attribute(null, ATTR_ID, id);
430         serializer.attribute(null, ATTR_NAME, name);
431         setValueAttribute(version, serializer, value);
432         serializer.attribute(null, ATTR_PACKAGE, packageName);
433         serializer.endTag(null, TAG_SETTING);
434     }
435
436     static void setValueAttribute(int version, XmlSerializer serializer, String value)
437             throws IOException {
438         if (version >= SETTINGS_VERSOIN_NEW_ENCODING) {
439             if (value == null) {
440                 // Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64.
441             } else if (isBinary(value)) {
442                 serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value));
443             } else {
444                 serializer.attribute(null, ATTR_VALUE, value);
445             }
446         } else {
447             // Old encoding.
448             if (value == null) {
449                 serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE);
450             } else {
451                 serializer.attribute(null, ATTR_VALUE, value);
452             }
453         }
454     }
455
456     private String getValueAttribute(XmlPullParser parser) {
457         if (mVersion >= SETTINGS_VERSOIN_NEW_ENCODING) {
458             final String value = parser.getAttributeValue(null, ATTR_VALUE);
459             if (value != null) {
460                 return value;
461             }
462             final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64);
463             if (base64 != null) {
464                 return base64Decode(base64);
465             }
466             // null has neither ATTR_VALUE nor ATTR_VALUE_BASE64.
467             return null;
468         } else {
469             // Old encoding.
470             final String stored = parser.getAttributeValue(null, ATTR_VALUE);
471             if (NULL_VALUE_OLD_STYLE.equals(stored)) {
472                 return null;
473             } else {
474                 return stored;
475             }
476         }
477     }
478
479     private void readStateSyncLocked() {
480         FileInputStream in;
481         if (!mStatePersistFile.exists()) {
482             return;
483         }
484         try {
485             in = new AtomicFile(mStatePersistFile).openRead();
486         } catch (FileNotFoundException fnfe) {
487             Slog.i(LOG_TAG, "No settings state");
488             return;
489         }
490         try {
491             XmlPullParser parser = Xml.newPullParser();
492             parser.setInput(in, StandardCharsets.UTF_8.name());
493             parseStateLocked(parser);
494
495         } catch (XmlPullParserException | IOException e) {
496             throw new IllegalStateException("Failed parsing settings file: "
497                     + mStatePersistFile , e);
498         } finally {
499             IoUtils.closeQuietly(in);
500         }
501     }
502
503     private void parseStateLocked(XmlPullParser parser)
504             throws IOException, XmlPullParserException {
505         final int outerDepth = parser.getDepth();
506         int type;
507         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
508                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
509             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
510                 continue;
511             }
512
513             String tagName = parser.getName();
514             if (tagName.equals(TAG_SETTINGS)) {
515                 parseSettingsLocked(parser);
516             }
517         }
518     }
519
520     private void parseSettingsLocked(XmlPullParser parser)
521             throws IOException, XmlPullParserException {
522
523         mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
524
525         final int outerDepth = parser.getDepth();
526         int type;
527         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
528                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
529             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
530                 continue;
531             }
532
533             String tagName = parser.getName();
534             if (tagName.equals(TAG_SETTING)) {
535                 String id = parser.getAttributeValue(null, ATTR_ID);
536                 String name = parser.getAttributeValue(null, ATTR_NAME);
537                 String value = getValueAttribute(parser);
538                 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
539                 mSettings.put(name, new Setting(name, value, packageName, id));
540
541                 if (DEBUG_PERSISTENCE) {
542                     Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
543                 }
544             }
545         }
546     }
547
548     private final class MyHandler extends Handler {
549         public static final int MSG_PERSIST_SETTINGS = 1;
550
551         public MyHandler(Looper looper) {
552             super(looper);
553         }
554
555         @Override
556         public void handleMessage(Message message) {
557             switch (message.what) {
558                 case MSG_PERSIST_SETTINGS: {
559                     Runnable callback = (Runnable) message.obj;
560                     doWriteState();
561                     if (callback != null) {
562                         callback.run();
563                     }
564                 }
565                 break;
566             }
567         }
568     }
569
570     class Setting {
571         private String name;
572         private String value;
573         private String packageName;
574         private String id;
575
576         public Setting(Setting other) {
577             name = other.name;
578             value = other.value;
579             packageName = other.packageName;
580             id = other.id;
581         }
582
583         public Setting(String name, String value, String packageName) {
584             init(name, value, packageName, String.valueOf(mNextId++));
585         }
586
587         public Setting(String name, String value, String packageName, String id) {
588             mNextId = Math.max(mNextId, Long.valueOf(id) + 1);
589             init(name, value, packageName, id);
590         }
591
592         private void init(String name, String value, String packageName, String id) {
593             this.name = name;
594             this.value = value;
595             this.packageName = packageName;
596             this.id = id;
597         }
598
599         public String getName() {
600             return name;
601         }
602
603         public int getkey() {
604             return mKey;
605         }
606
607         public String getValue() {
608             return value;
609         }
610
611         public String getPackageName() {
612             return packageName;
613         }
614
615         public String getId() {
616             return id;
617         }
618
619         public boolean isNull() {
620             return false;
621         }
622
623         public boolean update(String value, String packageName) {
624             if (Objects.equal(value, this.value)) {
625                 return false;
626             }
627             this.value = value;
628             this.packageName = packageName;
629             this.id = String.valueOf(mNextId++);
630             return true;
631         }
632     }
633
634     /**
635      * @return TRUE if a string is considered "binary" from KXML's point of view.  NOTE DO NOT
636      * pass null.
637      */
638     public static boolean isBinary(String s) {
639         if (s == null) {
640             throw new NullPointerException();
641         }
642         // See KXmlSerializer.writeEscaped
643         for (int i = 0; i < s.length(); i++) {
644             char c = s.charAt(i);
645             boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
646             if (!allowedInXml) {
647                 return true;
648             }
649         }
650         return false;
651     }
652
653     private static String base64Encode(String s) {
654         return Base64.encodeToString(toBytes(s), Base64.NO_WRAP);
655     }
656
657     private static String base64Decode(String s) {
658         return fromBytes(Base64.decode(s, Base64.DEFAULT));
659     }
660
661     // Note the followings are basically just UTF-16 encode/decode.  But we want to preserve
662     // contents as-is, even if it contains broken surrogate pairs, we do it by ourselves,
663     // since I don't know how Charset would treat them.
664
665     private static byte[] toBytes(String s) {
666         final byte[] result = new byte[s.length() * 2];
667         int resultIndex = 0;
668         for (int i = 0; i < s.length(); ++i) {
669             char ch = s.charAt(i);
670             result[resultIndex++] = (byte) (ch >> 8);
671             result[resultIndex++] = (byte) ch;
672         }
673         return result;
674     }
675
676     private static String fromBytes(byte[] bytes) {
677         final StringBuffer sb = new StringBuffer(bytes.length / 2);
678
679         final int last = bytes.length - 1;
680
681         for (int i = 0; i < last; i += 2) {
682             final char ch = (char) ((bytes[i] & 0xff) << 8 | (bytes[i + 1] & 0xff));
683             sb.append(ch);
684         }
685         return sb.toString();
686     }
687 }