• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.internal.util.test;
18 
19 import android.net.Uri;
20 import android.os.Bundle;
21 import android.provider.Settings;
22 import android.test.mock.MockContentProvider;
23 import android.util.Log;
24 
25 import 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  */
67 public 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 
FakeSettingsProvider()75     public FakeSettingsProvider() {
76         for (int i = 0; i < TABLES.length; i++) {
77             mTables.put(TABLES[i], new HashMap<String, String>());
78         }
79     }
80 
getUriFor(String table, String key)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      * Creates a {@link org.junit.rules.TestRule} that makes sure {@link #clearSettingsProvider()}
96      * is triggered before and after each test.
97      */
rule()98     public static FakeSettingsProviderRule rule() {
99         return new FakeSettingsProviderRule();
100     }
101 
102     /**
103      * This needs to be called before and after using the FakeSettingsProvider class.
104      */
clearSettingsProvider()105     public static void clearSettingsProvider() {
106         Settings.Secure.clearProviderForTest();
107         Settings.Global.clearProviderForTest();
108         Settings.System.clearProviderForTest();
109     }
110 
call(String method, String arg, Bundle extras)111     public Bundle call(String method, String arg, Bundle extras) {
112         // Methods are "GET_system", "GET_global", "PUT_secure", etc.
113         String[] commands = method.split("_", 2);
114         String op = commands[0];
115         String table = commands[1];
116 
117         Bundle out = new Bundle();
118         String value;
119         switch (op) {
120             case "GET":
121                 value = mTables.get(table).get(arg);
122                 if (value != null) {
123                     if (DBG) {
124                         Log.d(TAG, String.format("Returning fake setting %s.%s = %s",
125                                 table, arg, value));
126                     }
127                     out.putString(Settings.NameValueTable.VALUE, value);
128                 }
129                 break;
130             case "PUT":
131                 value = extras.getString(Settings.NameValueTable.VALUE, null);
132                 if (DBG) {
133                     Log.d(TAG, String.format("Inserting fake setting %s.%s = %s",
134                             table, arg, value));
135                 }
136                 if (value != null) {
137                     mTables.get(table).put(arg, value);
138                 } else {
139                     mTables.get(table).remove(arg);
140                 }
141                 break;
142             default:
143                 throw new UnsupportedOperationException("Unknown command " + method);
144         }
145 
146         return out;
147     }
148 }
149