• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package android.car.test.mocks;
17 
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.anyInt;
20 import static org.mockito.ArgumentMatchers.anyString;
21 import static org.mockito.Mockito.when;
22 
23 import android.annotation.Nullable;
24 import android.car.test.mocks.AbstractExtendedMockitoTestCase.CustomMockitoSessionBuilder;
25 import android.provider.Settings;
26 import android.util.ArrayMap;
27 import android.util.Log;
28 
29 import com.android.internal.util.Preconditions;
30 
31 import org.mockito.invocation.InvocationOnMock;
32 import org.mockito.stubbing.Answer;
33 
34 
35 // TODO (b/156033195): Clean settings API. For example, don't mock xyzForUser() methods (as
36 // they should not be used due to mainline) and explicitly use a MockSettings per user or
37 // something like that (to make sure the code being test is passing the writer userId to
38 // Context.createContextAsUser())
39 public final class MockSettings {
40 
41     private static final String TAG = MockSettings.class.getSimpleName();
42     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
43 
44     private static final int INVALID_DEFAULT_INDEX = -1;
45 
46     private final ArrayMap<String, Object> mSettingsMapping = new ArrayMap<>();
47 
MockSettings(CustomMockitoSessionBuilder sessionBuilder)48     public MockSettings(CustomMockitoSessionBuilder sessionBuilder) {
49         // TODO (b/156033195): change from mock to spy - or don't use mock at all
50         sessionBuilder
51                 .mockStatic(Settings.Global.class)
52                 .mockStatic(Settings.System.class)
53                 .mockStatic(Settings.Secure.class);
54         sessionBuilder.setSessionCallback(() -> setExpectations());
55     }
56 
setExpectations()57     private void setExpectations() {
58         Answer<Object> insertObjectAnswer =
59                 invocation -> insertObjectFromInvocation(invocation, 1, 2);
60         Answer<Integer> getIntAnswer = invocation ->
61                 getAnswer(invocation, Integer.class, 1, 2);
62         Answer<String> getStringAnswer = invocation ->
63                 getAnswer(invocation, String.class, 1, INVALID_DEFAULT_INDEX);
64 
65         when(Settings.Global.putInt(any(), any(), anyInt())).thenAnswer(insertObjectAnswer);
66 
67         when(Settings.Global.getInt(any(), any(), anyInt())).thenAnswer(getIntAnswer);
68 
69         when(Settings.System.putInt(any(), any(), anyInt())).thenAnswer(insertObjectAnswer);
70 
71         when(Settings.System.getInt(any(), any(), anyInt())).thenAnswer(getIntAnswer);
72 
73         when(Settings.Secure.putIntForUser(any(), any(), anyInt(), anyInt()))
74                 .thenAnswer(insertObjectAnswer);
75 
76         when(Settings.Secure.getIntForUser(any(), any(), anyInt(), anyInt()))
77                 .thenAnswer(getIntAnswer);
78 
79         when(Settings.Secure.getInt(any(), any(), anyInt())).thenAnswer(getIntAnswer);
80 
81         when(Settings.Secure.putStringForUser(any(), anyString(), anyString(), anyInt()))
82                 .thenAnswer(insertObjectAnswer);
83 
84         when(Settings.Global.putString(any(), any(), any()))
85                 .thenAnswer(insertObjectAnswer);
86 
87         when(Settings.Global.getString(any(), any())).thenAnswer(getStringAnswer);
88 
89         when(Settings.System.putIntForUser(any(), any(), anyInt(), anyInt()))
90                 .thenAnswer(insertObjectAnswer);
91 
92         when(Settings.System.getIntForUser(any(), any(), anyInt(), anyInt()))
93                 .thenAnswer(getIntAnswer);
94 
95         when(Settings.System.putStringForUser(any(), any(), anyString(), anyInt()))
96                 .thenAnswer(insertObjectAnswer);
97 
98         when(Settings.System.putString(any(), any(), any()))
99                 .thenAnswer(insertObjectAnswer);
100     }
101 
insertObjectFromInvocation(InvocationOnMock invocation, int keyIndex, int valueIndex)102     private Object insertObjectFromInvocation(InvocationOnMock invocation, int keyIndex,
103             int valueIndex) {
104         String key = (String) invocation.getArguments()[keyIndex];
105         Object value = invocation.getArguments()[valueIndex];
106         insertObject(key, value);
107         // NOTE:  the return value is not really used but it's needed so it can be used as a lambda
108         // for Answer<REAL_TYPE>. In fact, returning `value` would cause tests to fail due to
109         // invalid casts at runtime.
110         return null;
111     }
112 
insertObject(String key, Object value)113     private void insertObject(String key, Object value) {
114         if (VERBOSE) {
115             Log.v(TAG, "Inserting Setting " + key + ": " + value);
116         }
117         mSettingsMapping.put(key, value);
118     }
119 
getAnswer(InvocationOnMock invocation, Class<T> clazz, int keyIndex, int defaultValueIndex)120     private <T> T getAnswer(InvocationOnMock invocation, Class<T> clazz, int keyIndex,
121             int defaultValueIndex) {
122         String key = (String) invocation.getArguments()[keyIndex];
123         T defaultValue = null;
124         if (defaultValueIndex > INVALID_DEFAULT_INDEX) {
125             defaultValue = safeCast(invocation.getArguments()[defaultValueIndex], clazz);
126         }
127         return get(key, defaultValue, clazz);
128     }
129 
130     @Nullable
get(String key, T defaultValue, Class<T> clazz)131     private <T> T get(String key, T defaultValue, Class<T> clazz) {
132         if (VERBOSE) {
133             Log.v(TAG, "get(): key=" + key + ", default=" + defaultValue + ", class=" + clazz);
134         }
135         Object value = mSettingsMapping.get(key);
136         if (value == null) {
137             if (VERBOSE) {
138                 Log.v(TAG, "not found");
139             }
140             return defaultValue;
141         }
142 
143         if (VERBOSE) {
144             Log.v(TAG, "returning " + value);
145         }
146         return safeCast(value, clazz);
147     }
148 
safeCast(Object value, Class<T> clazz)149     private static <T> T safeCast(Object value, Class<T> clazz) {
150         if (value == null) {
151             return null;
152         }
153         Preconditions.checkArgument(value.getClass() == clazz,
154                 "Setting value has class %s but requires class %s",
155                 value.getClass(), clazz);
156         return clazz.cast(value);
157     }
158 
159     /**
160      * Adds key-value(int) pair in mocked Settings.Global and Settings.Secure
161      */
putInt(String key, int value)162     public void putInt(String key, int value) {
163         insertObject(key, value);
164     }
165 
166     /**
167      * Adds key-value(String) pair in mocked Settings.Global and Settings.Secure
168      */
putString(String key, String value)169     public void putString(String key, String value) {
170         insertObject(key, value);
171     }
172 
getString(String key)173     public String getString(String key) {
174         return get(key, null, String.class);
175     }
176 
getInt(String key)177     public int getInt(String key) {
178         return get(key, null, Integer.class);
179     }
180 
assertDoesNotContainsKey(String key)181     public void assertDoesNotContainsKey(String key) {
182         if (mSettingsMapping.containsKey(key)) {
183             throw new AssertionError("Should not have key " + key + ", but has: "
184                     + mSettingsMapping.get(key));
185         }
186     }
187 }
188