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
15package android.testing;
16
17import android.content.ContentProviderClient;
18import android.content.Context;
19import android.os.Bundle;
20import android.os.RemoteException;
21import android.os.UserHandle;
22import android.provider.Settings;
23import android.test.mock.MockContentProvider;
24import android.util.Log;
25
26import java.util.HashMap;
27
28import static org.junit.Assert.*;
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 */
36public 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
47    private TestableSettingsProvider(ContentProviderClient settings) {
48        mSettings = settings;
49    }
50
51    void clearValuesAndCheck(Context context) {
52        int userId = UserHandle.myUserId();
53        mValues.put(key("global", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
54        mValues.put(key("secure", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
55        mValues.put(key("system", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
56
57        // Verify that if any test is using TestableContext, they all have the correct settings
58        // provider.
59        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
60                Settings.Global.getString(context.getContentResolver(), MY_UNIQUE_KEY));
61        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
62                Settings.Secure.getString(context.getContentResolver(), MY_UNIQUE_KEY));
63        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
64                Settings.System.getString(context.getContentResolver(), MY_UNIQUE_KEY));
65
66        mValues.clear();
67    }
68
69    public Bundle call(String method, String arg, Bundle extras) {
70        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
71        final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, 0);
72        final String[] commands = method.split("_", 2);
73        final String op = commands[0];
74        final String table = commands[1];
75
76            String k = key(table, arg, userId);
77            String value;
78            Bundle out = new Bundle();
79            switch (op) {
80                case "GET":
81                    if (mValues.containsKey(k)) {
82                        value = mValues.get(k);
83                        if (value != null) {
84                            out.putString(Settings.NameValueTable.VALUE, value);
85                        }
86                    } else {
87                        // Fall through to real settings.
88                        try {
89                            if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
90                            // TODO: Add our own version of caching to handle this.
91                            Bundle call = mSettings.call(method, arg, extras);
92                            call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
93                            return call;
94                        } catch (RemoteException e) {
95                            throw new RuntimeException(e);
96                        }
97                    }
98                    break;
99                case "PUT":
100                    value = extras.getString(Settings.NameValueTable.VALUE, null);
101                    mValues.put(k, value);
102                    break;
103                default:
104                    throw new UnsupportedOperationException("Unknown command " + method);
105            }
106            return out;
107    }
108
109    private static String key(String table, String key, int userId) {
110        if ("global".equals(table)) {
111            return table + "_" + key;
112        } else {
113            return table + "_" + userId + "_" + key;
114        }
115
116    }
117
118    /**
119     * Since the settings provider is cached inside android.provider.Settings, this must
120     * be gotten statically to ensure there is only one instance referenced.
121     */
122    static TestableSettingsProvider getFakeSettingsProvider(ContentProviderClient settings) {
123        if (sInstance == null) {
124            sInstance = new TestableSettingsProvider(settings);
125        }
126        return sInstance;
127    }
128}
129