1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package android.testing;
16 
17 import static org.junit.Assert.assertEquals;
18 
19 import android.content.ContentProviderClient;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.RemoteException;
23 import android.os.UserHandle;
24 import android.provider.Settings;
25 import android.test.mock.MockContentProvider;
26 import android.util.Log;
27 
28 import java.util.HashMap;
29 
30 /**
31  * Allows calls to android.provider.Settings to be tested easier.
32  *
33  * This provides a simple copy-on-write implementation of settings that gets cleared
34  * at the end of each test.
35  */
36 public class TestableSettingsProvider extends MockContentProvider {
37 
38     private static final String TAG = "TestableSettingsProvider";
39     private static final boolean DEBUG = false;
40     private static final String MY_UNIQUE_KEY = "Key_" + TestableSettingsProvider.class.getName();
41     private static TestableSettingsProvider sInstance;
42 
43     private final ContentProviderClient mSettings;
44 
45     private final HashMap<String, String> mValues = new HashMap<>();
46 
TestableSettingsProvider(ContentProviderClient settings)47     private TestableSettingsProvider(ContentProviderClient settings) {
48         mSettings = settings;
49     }
50 
clearValuesAndCheck(Context context)51     void clearValuesAndCheck(Context context) {
52         // Ensure we swapped over to use TestableSettingsProvider
53         Settings.Global.clearProviderForTest();
54         Settings.Secure.clearProviderForTest();
55         Settings.System.clearProviderForTest();
56 
57         // putString will eventually invoking the mocked call() method and update mValues
58         Settings.Global.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
59         Settings.Secure.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
60         Settings.System.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
61         // Verify that if any test is using TestableContext, they all have the correct settings
62         // provider.
63         assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
64                 Settings.Global.getString(context.getContentResolver(), MY_UNIQUE_KEY));
65         assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
66                 Settings.Secure.getString(context.getContentResolver(), MY_UNIQUE_KEY));
67         assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
68                 Settings.System.getString(context.getContentResolver(), MY_UNIQUE_KEY));
69 
70         mValues.clear();
71     }
72 
call(String method, String arg, Bundle extras)73     public Bundle call(String method, String arg, Bundle extras) {
74         // Methods are "GET_system", "GET_global", "PUT_secure", etc.
75         int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT);
76         if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) {
77             userId = UserHandle.myUserId();
78         }
79 
80         final String[] commands = method.split("_", 2);
81         final String op = commands[0];
82         final String table = commands[1];
83 
84             String k = key(table, arg, userId);
85             String value;
86             Bundle out = new Bundle();
87             switch (op) {
88                 case "GET":
89                     if (mValues.containsKey(k)) {
90                         value = mValues.get(k);
91                         if (value != null) {
92                             out.putString(Settings.NameValueTable.VALUE, value);
93                         }
94                     } else {
95                         // Fall through to real settings.
96                         try {
97                             if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
98                             // TODO: Add our own version of caching to handle this.
99                             Bundle call = mSettings.call(method, arg, extras);
100                             call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
101                             return call;
102                         } catch (RemoteException e) {
103                             throw new RuntimeException(e);
104                         }
105                     }
106                     break;
107                 case "PUT":
108                     value = extras.getString(Settings.NameValueTable.VALUE, null);
109                     mValues.put(k, value);
110                     break;
111                 default:
112                     throw new UnsupportedOperationException("Unknown command " + method);
113             }
114             return out;
115     }
116 
key(String table, String key, int userId)117     private static String key(String table, String key, int userId) {
118         if ("global".equals(table)) {
119             return table + "_" + key;
120         } else {
121             return table + "_" + userId + "_" + key;
122         }
123 
124     }
125 
126     /**
127      * Since the settings provider is cached inside android.provider.Settings, this must
128      * be gotten statically to ensure there is only one instance referenced.
129      */
getFakeSettingsProvider(ContentProviderClient settings)130     static TestableSettingsProvider getFakeSettingsProvider(ContentProviderClient settings) {
131         if (sInstance == null) {
132             sInstance = new TestableSettingsProvider(settings);
133         }
134         return sInstance;
135     }
136 }
137