FakeSettingsProvider.java revision aa3c30dfa2459fb093ac5d7075b5066f41b49f2e
1/*
2 * Copyright (C) 2016 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 com.android.internal.util.test;
18
19import android.net.Uri;
20import android.os.Bundle;
21import android.provider.Settings;
22import android.test.mock.MockContentProvider;
23import android.util.Log;
24
25import java.util.HashMap;
26
27/**
28 * Fake for system settings.
29 *
30 * To use, ensure that the Context used by the test code returns a ContentResolver that uses this
31 * provider for the Settings authority:
32 *
33 *   class MyTestContext extends MockContext {
34 *       ...
35 *       private final MockContentResolver mContentResolver;
36 *       public MyTestContext(...) {
37 *           ...
38 *           mContentResolver = new MockContentResolver();
39 *           mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
40 *       }
41 *       ...
42 *       @Override
43 *       public ContentResolver getContentResolver() {
44 *           return mContentResolver;
45 *       }
46 *
47 * As long as the code under test is using the test Context, the actual code under test does not
48 * need to be modified, and can access Settings using the normal static methods:
49 *
50 *   Settings.Global.getInt(cr, "my_setting", 0);  // Returns 0.
51 *   Settings.Global.putInt(cr, "my_setting", 5);
52 *   Settings.Global.getInt(cr, "my_setting", 0);  // Returns 5.
53 *
54 * Note that this class cannot be used in the same process as real settings. This is because it
55 * works by passing an alternate ContentResolver to Settings operations. Unfortunately, the Settings
56 * class only fetches the content provider from the passed-in ContentResolver the first time it's
57 * used, and after that stores it in a per-process static. If this needs to be used in this case,
58 * then call {@link #clearSettingsProvider()} before and after using this.
59 *
60 * TODO: evaluate implementing settings change notifications. This would require:
61 *
62 * 1. Making ContentResolver#registerContentObserver non-final and overriding it in
63 *    MockContentResolver.
64 * 2. Making FakeSettingsProvider take a ContentResolver argument.
65 * 3. Calling ContentResolver#notifyChange(getUriFor(table, arg), ...) on every settings change.
66 */
67public class FakeSettingsProvider extends MockContentProvider {
68
69    private static final String TAG = FakeSettingsProvider.class.getSimpleName();
70    private static final boolean DBG = false;
71    private static final String[] TABLES = { "system", "secure", "global" };
72
73    private final HashMap<String, HashMap<String, String>> mTables = new HashMap<>();
74
75    public FakeSettingsProvider() {
76        for (int i = 0; i < TABLES.length; i++) {
77            mTables.put(TABLES[i], new HashMap<String, String>());
78        }
79    }
80
81    private Uri getUriFor(String table, String key) {
82        switch (table) {
83            case "system":
84                return Settings.System.getUriFor(key);
85            case "secure":
86                return Settings.Secure.getUriFor(key);
87            case "global":
88                return Settings.Global.getUriFor(key);
89            default:
90                throw new UnsupportedOperationException("Unknown settings table " + table);
91        }
92    }
93
94    /**
95     * This needs to be called before and after using the FakeSettingsProvider class.
96     */
97    public static void clearSettingsProvider() {
98        Settings.Secure.clearProviderForTest();
99        Settings.Global.clearProviderForTest();
100        Settings.System.clearProviderForTest();
101    }
102
103    public Bundle call(String method, String arg, Bundle extras) {
104        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
105        String[] commands = method.split("_", 2);
106        String op = commands[0];
107        String table = commands[1];
108
109        Bundle out = new Bundle();
110        String value;
111        switch (op) {
112            case "GET":
113                value = mTables.get(table).get(arg);
114                if (value != null) {
115                    if (DBG) {
116                        Log.d(TAG, String.format("Returning fake setting %s.%s = %s",
117                                table, arg, value));
118                    }
119                    out.putString(Settings.NameValueTable.VALUE, value);
120                }
121                break;
122            case "PUT":
123                value = extras.getString(Settings.NameValueTable.VALUE, null);
124                if (DBG) {
125                    Log.d(TAG, String.format("Inserting fake setting %s.%s = %s",
126                            table, arg, value));
127                }
128                if (value != null) {
129                    mTables.get(table).put(arg, value);
130                } else {
131                    mTables.get(table).remove(arg);
132                }
133                break;
134            default:
135                throw new UnsupportedOperationException("Unknown command " + method);
136        }
137
138        return out;
139    }
140}
141