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