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
17package com.android.internal.telephony;
18
19import com.google.common.collect.ArrayListMultimap;
20import com.google.common.collect.Multimap;
21
22import org.mockito.MockitoAnnotations;
23import org.mockito.invocation.InvocationOnMock;
24import org.mockito.stubbing.Answer;
25
26import android.app.AlarmManager;
27import android.app.AppOpsManager;
28import android.app.NotificationManager;
29import android.app.usage.UsageStatsManager;
30import android.content.BroadcastReceiver;
31import android.content.ComponentName;
32import android.content.ContentProvider;
33import android.content.ContentResolver;
34import android.content.ContentValues;
35import android.content.Context;
36import android.content.Intent;
37import android.content.IntentFilter;
38import android.content.ServiceConnection;
39import android.content.SharedPreferences;
40import android.content.pm.PackageManager;
41import android.content.pm.ResolveInfo;
42import android.content.pm.ServiceInfo;
43import android.content.res.Configuration;
44import android.content.res.Resources;
45import android.database.Cursor;
46import android.database.MatrixCursor;
47import android.net.ConnectivityManager;
48import android.net.Uri;
49import android.net.wifi.WifiManager;
50import android.os.Bundle;
51import android.os.Handler;
52import android.os.IInterface;
53import android.os.PersistableBundle;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.PreferenceManager;
57import android.provider.Settings;
58import android.telephony.CarrierConfigManager;
59import android.telephony.SubscriptionManager;
60import android.telephony.TelephonyManager;
61import android.test.mock.MockContentProvider;
62import android.test.mock.MockContentResolver;
63import android.test.mock.MockContext;
64import android.util.Log;
65
66import java.util.ArrayList;
67import java.util.Arrays;
68import java.util.Collection;
69import java.util.HashMap;
70import java.util.HashSet;
71import java.util.List;
72import java.util.Locale;
73import java.util.Map;
74
75import static org.mockito.Mockito.any;
76import static org.mockito.Mockito.anyInt;
77import static org.mockito.Mockito.doAnswer;
78import static org.mockito.Mockito.doReturn;
79import static org.mockito.Mockito.eq;
80import static org.mockito.Mockito.mock;
81import static org.mockito.Mockito.spy;
82import static org.mockito.Mockito.when;
83
84/**
85 * Controls a test {@link Context} as would be provided by the Android framework to an
86 * {@code Activity}, {@code Service} or other system-instantiated component.
87 *
88 * Contains Fake<Component> classes like FakeContext for components that require complex and
89 * reusable stubbing. Others can be mocked using Mockito functions in tests or constructor/public
90 * methods of this class.
91 */
92public class ContextFixture implements TestFixture<Context> {
93    private static final String TAG = "ContextFixture";
94    public static final String PERMISSION_ENABLE_ALL = "android.permission.STUB_PERMISSION";
95
96    public class FakeContentProvider extends MockContentProvider {
97        private String[] mColumns = {"name", "value"};
98        private HashMap<String, String> mKeyValuePairs = new HashMap<String, String>();
99        private int mNumKeyValuePairs = 0;
100
101        @Override
102        public int delete(Uri uri, String selection, String[] selectionArgs) {
103            return 0;
104        }
105
106        @Override
107        public Uri insert(Uri uri, ContentValues values) {
108            Uri newUri = null;
109            if (values != null) {
110                mKeyValuePairs.put(values.getAsString("name"), values.getAsString("value"));
111                mNumKeyValuePairs++;
112                newUri = Uri.withAppendedPath(uri, "" + mNumKeyValuePairs);
113            }
114            logd("insert called, new mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri +
115                    " newUri: " + newUri);
116            return newUri;
117        }
118
119        @Override
120        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
121                            String sortOrder) {
122            //assuming query will always be of the form 'name = ?'
123            logd("query called, mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri);
124            if (mKeyValuePairs.containsKey(selectionArgs[0])) {
125                MatrixCursor cursor = new MatrixCursor(projection);
126                cursor.addRow(new String[]{mKeyValuePairs.get(selectionArgs[0])});
127                return cursor;
128            }
129            return null;
130        }
131
132        @Override
133        public Bundle call(String method, String request, Bundle args) {
134            logd("call called, mNumKeyValuePairs: " + mNumKeyValuePairs + " method: " + method +
135                    " request: " + request);
136            switch(method) {
137                case Settings.CALL_METHOD_GET_GLOBAL:
138                case Settings.CALL_METHOD_GET_SECURE:
139                case Settings.CALL_METHOD_GET_SYSTEM:
140                    if (mKeyValuePairs.containsKey(request)) {
141                        Bundle b = new Bundle(1);
142                        b.putCharSequence("value", mKeyValuePairs.get(request));
143                        logd("returning value pair: " + mKeyValuePairs.get(request) + " for " +
144                                request);
145                        return b;
146                    }
147                    break;
148                case Settings.CALL_METHOD_PUT_GLOBAL:
149                case Settings.CALL_METHOD_PUT_SECURE:
150                case Settings.CALL_METHOD_PUT_SYSTEM:
151                    logd("adding key-value pair: " + request + "-" + (String)args.get("value"));
152                    mKeyValuePairs.put(request, (String)args.get("value"));
153                    mNumKeyValuePairs++;
154                    break;
155            }
156            return null;
157        }
158    }
159
160    private final HashMap<String, Object> mSystemServices = new HashMap<String, Object>();
161
162    public void setSystemService(String name, Object service) {
163        synchronized (mSystemServices) {
164            mSystemServices.put(name, service);
165        }
166    }
167
168    public class FakeContext extends MockContext {
169        @Override
170        public PackageManager getPackageManager() {
171            return mPackageManager;
172        }
173
174        @Override
175        public boolean bindService(
176                Intent serviceIntent,
177                ServiceConnection connection,
178                int flags) {
179            if (mServiceByServiceConnection.containsKey(connection)) {
180                throw new RuntimeException("ServiceConnection already bound: " + connection);
181            }
182            IInterface service = mServiceByComponentName.get(serviceIntent.getComponent());
183            if (service == null) {
184                throw new RuntimeException("ServiceConnection not found: "
185                        + serviceIntent.getComponent());
186            }
187            mServiceByServiceConnection.put(connection, service);
188            connection.onServiceConnected(serviceIntent.getComponent(), service.asBinder());
189            return true;
190        }
191
192        @Override
193        public void unbindService(
194                ServiceConnection connection) {
195            IInterface service = mServiceByServiceConnection.remove(connection);
196            if (service == null) {
197                throw new RuntimeException("ServiceConnection not found: " + connection);
198            }
199            connection.onServiceDisconnected(mComponentNameByService.get(service));
200        }
201
202        @Override
203        public Object getSystemService(String name) {
204            synchronized (mSystemServices) {
205                Object service = mSystemServices.get(name);
206                if (service != null) return service;
207            }
208            switch (name) {
209                case Context.TELEPHONY_SERVICE:
210                    return mTelephonyManager;
211                case Context.APP_OPS_SERVICE:
212                    return mAppOpsManager;
213                case Context.NOTIFICATION_SERVICE:
214                    return mNotificationManager;
215                case Context.USER_SERVICE:
216                    return mUserManager;
217                case Context.CARRIER_CONFIG_SERVICE:
218                    return mCarrierConfigManager;
219                case Context.POWER_SERVICE:
220                    // PowerManager is a final class so cannot be mocked, return real service
221                    return TestApplication.getAppContext().getSystemService(name);
222                case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
223                    return mSubscriptionManager;
224                case Context.WIFI_SERVICE:
225                    return mWifiManager;
226                case Context.ALARM_SERVICE:
227                    return mAlarmManager;
228                case Context.CONNECTIVITY_SERVICE:
229                    return mConnectivityManager;
230                case Context.USAGE_STATS_SERVICE:
231                    return mUsageStatManager;
232                default:
233                    return null;
234            }
235        }
236
237        @Override
238        public int getUserId() {
239            return 0;
240        }
241
242        @Override
243        public Resources getResources() {
244            return mResources;
245        }
246
247        @Override
248        public String getOpPackageName() {
249            return "com.android.internal.telephony";
250        }
251
252        @Override
253        public ContentResolver getContentResolver() {
254            return mContentResolver;
255        }
256
257        @Override
258        public void unregisterReceiver(BroadcastReceiver receiver) {
259        }
260
261        @Override
262        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
263            return registerReceiver(receiver, filter, null, null);
264        }
265
266        @Override
267        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
268                String broadcastPermission, Handler scheduler) {
269            return registerReceiverAsUser(receiver, null, filter, broadcastPermission, scheduler);
270        }
271
272        @Override
273        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
274                IntentFilter filter, String broadcastPermission, Handler scheduler) {
275            Intent result = null;
276            synchronized (mBroadcastReceiversByAction) {
277                for (int i = 0 ; i < filter.countActions() ; i++) {
278                    mBroadcastReceiversByAction.put(filter.getAction(i), receiver);
279                    if (result == null) {
280                        result = mStickyBroadcastByAction.get(filter.getAction(i));
281                    }
282                }
283            }
284
285            return result;
286        }
287
288        @Override
289        public void sendBroadcast(Intent intent) {
290            logd("sendBroadcast called for " + intent.getAction());
291            synchronized (mBroadcastReceiversByAction) {
292                for (BroadcastReceiver broadcastReceiver :
293                        mBroadcastReceiversByAction.get(intent.getAction())) {
294                    broadcastReceiver.onReceive(mContext, intent);
295                }
296            }
297        }
298
299        @Override
300        public void sendBroadcast(Intent intent, String receiverPermission) {
301            logd("sendBroadcast called for " + intent.getAction());
302            sendBroadcast(intent);
303        }
304
305        @Override
306        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
307            sendBroadcast(intent);
308        }
309
310        @Override
311        public void sendBroadcastAsUser(Intent intent, UserHandle user,
312                                        String receiverPermission) {
313            sendBroadcast(intent);
314        }
315
316        @Override
317        public void sendBroadcastAsUser(Intent intent, UserHandle user,
318                                        String receiverPermission, int appOp) {
319            sendBroadcast(intent);
320        }
321
322        @Override
323        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
324                String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
325                int initialCode, String initialData, Bundle initialExtras) {
326            logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
327            sendBroadcast(intent);
328            if (resultReceiver != null) {
329                synchronized (mOrderedBroadcastReceivers) {
330                    mOrderedBroadcastReceivers.put(intent, resultReceiver);
331                }
332            }
333        }
334
335        @Override
336        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
337                String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
338                Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
339            logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
340            sendBroadcast(intent);
341            if (resultReceiver != null) {
342                synchronized (mOrderedBroadcastReceivers) {
343                    mOrderedBroadcastReceivers.put(intent, resultReceiver);
344                }
345            }
346        }
347
348        @Override
349        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
350                String receiverPermission, int appOp, Bundle options,
351                BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
352                String initialData, Bundle initialExtras) {
353            logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
354            sendBroadcast(intent);
355            if (resultReceiver != null) {
356                synchronized (mOrderedBroadcastReceivers) {
357                    mOrderedBroadcastReceivers.put(intent, resultReceiver);
358                }
359            }
360        }
361
362        @Override
363        public void sendStickyBroadcast(Intent intent) {
364            logd("sendStickyBroadcast called for " + intent.getAction());
365            synchronized (mBroadcastReceiversByAction) {
366                sendBroadcast(intent);
367                mStickyBroadcastByAction.put(intent.getAction(), intent);
368            }
369        }
370
371        @Override
372        public void sendStickyBroadcastAsUser(Intent intent, UserHandle ignored) {
373            logd("sendStickyBroadcastAsUser called for " + intent.getAction());
374            sendStickyBroadcast(intent);
375        }
376
377        @Override
378        public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
379                throws PackageManager.NameNotFoundException {
380            return this;
381        }
382
383        @Override
384        public void enforceCallingOrSelfPermission(String permission, String message) {
385            if (mPermissionTable.contains(permission)
386                    || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) {
387                return;
388            }
389            logd("requested permission: " + permission + " got denied");
390            throw new SecurityException(permission + " denied: " + message);
391        }
392
393        @Override
394        public int checkCallingOrSelfPermission(String permission) {
395            return PackageManager.PERMISSION_GRANTED;
396        }
397
398        @Override
399        public SharedPreferences getSharedPreferences(String name, int mode) {
400            return mSharedPreferences;
401        }
402
403        @Override
404        public String getPackageName() {
405            return "com.android.internal.telephony";
406        }
407
408        public boolean testMethod() {
409            return true;
410        }
411
412        public boolean testMethod1() {
413            return true;
414        }
415
416        public boolean testMethod2() {
417            return true;
418        }
419    }
420
421    private final Multimap<String, ComponentName> mComponentNamesByAction =
422            ArrayListMultimap.create();
423    private final Map<ComponentName, IInterface> mServiceByComponentName =
424            new HashMap<ComponentName, IInterface>();
425    private final Map<ComponentName, ServiceInfo> mServiceInfoByComponentName =
426            new HashMap<ComponentName, ServiceInfo>();
427    private final Map<IInterface, ComponentName> mComponentNameByService =
428            new HashMap<IInterface, ComponentName>();
429    private final Map<ServiceConnection, IInterface> mServiceByServiceConnection =
430            new HashMap<ServiceConnection, IInterface>();
431    private final Multimap<String, BroadcastReceiver> mBroadcastReceiversByAction =
432            ArrayListMultimap.create();
433    private final HashMap<String, Intent> mStickyBroadcastByAction =
434            new HashMap<String, Intent>();
435    private final Multimap<Intent, BroadcastReceiver> mOrderedBroadcastReceivers =
436            ArrayListMultimap.create();
437    private final HashSet<String> mPermissionTable = new HashSet<>();
438
439
440
441    // The application context is the most important object this class provides to the system
442    // under test.
443    private final Context mContext = spy(new FakeContext());
444
445    // We then create a spy on the application context allowing standard Mockito-style
446    // when(...) logic to be used to add specific little responses where needed.
447
448    private final Resources mResources = mock(Resources.class);
449    private final PackageManager mPackageManager = mock(PackageManager.class);
450    private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
451    private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
452    private final NotificationManager mNotificationManager = mock(NotificationManager.class);
453    private final UserManager mUserManager = mock(UserManager.class);
454    private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
455    private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
456    private final AlarmManager mAlarmManager = mock(AlarmManager.class);
457    private final ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
458    private final UsageStatsManager mUsageStatManager = null;
459    private final WifiManager mWifiManager = mock(WifiManager.class);
460
461    private final ContentProvider mContentProvider = spy(new FakeContentProvider());
462
463    private final Configuration mConfiguration = new Configuration();
464    private final SharedPreferences mSharedPreferences = PreferenceManager
465            .getDefaultSharedPreferences(TestApplication.getAppContext());
466    private final MockContentResolver mContentResolver = new MockContentResolver();
467    private final PersistableBundle mBundle = new PersistableBundle();
468
469    public ContextFixture() {
470        MockitoAnnotations.initMocks(this);
471
472        doAnswer(new Answer<List<ResolveInfo>>() {
473            @Override
474            public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
475                return doQueryIntentServices(
476                        (Intent) invocation.getArguments()[0],
477                        (Integer) invocation.getArguments()[1]);
478            }
479        }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt());
480
481        doAnswer(new Answer<List<ResolveInfo>>() {
482            @Override
483            public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
484                return doQueryIntentServices(
485                        (Intent) invocation.getArguments()[0],
486                        (Integer) invocation.getArguments()[1]);
487            }
488        }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
489
490        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
491
492        mConfiguration.locale = Locale.US;
493        doReturn(mConfiguration).when(mResources).getConfiguration();
494
495        mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
496        mPermissionTable.add(PERMISSION_ENABLE_ALL);
497    }
498
499    @Override
500    public Context getTestDouble() {
501        return mContext;
502    }
503
504    public void putResource(int id, final String value) {
505        when(mResources.getText(eq(id))).thenReturn(value);
506        when(mResources.getString(eq(id))).thenReturn(value);
507        when(mResources.getString(eq(id), any())).thenAnswer(new Answer<String>() {
508            @Override
509            public String answer(InvocationOnMock invocation) {
510                Object[] args = invocation.getArguments();
511                return String.format(value, Arrays.copyOfRange(args, 1, args.length));
512            }
513        });
514    }
515
516    public void putBooleanResource(int id, boolean value) {
517        when(mResources.getBoolean(eq(id))).thenReturn(value);
518    }
519
520    public void putStringArrayResource(int id, String[] values) {
521        doReturn(values).when(mResources).getStringArray(eq(id));
522    }
523
524    public void putIntArrayResource(int id, int[] values) {
525        doReturn(values).when(mResources).getIntArray(eq(id));
526    }
527
528    public PersistableBundle getCarrierConfigBundle() {
529        return mBundle;
530    }
531
532    private void addService(String action, ComponentName name, IInterface service) {
533        mComponentNamesByAction.put(action, name);
534        mServiceByComponentName.put(name, service);
535        mComponentNameByService.put(service, name);
536    }
537
538    private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
539        List<ResolveInfo> result = new ArrayList<ResolveInfo>();
540        for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
541            ResolveInfo resolveInfo = new ResolveInfo();
542            resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
543            result.add(resolveInfo);
544        }
545        return result;
546    }
547
548    public void sendBroadcastToOrderedBroadcastReceivers() {
549        synchronized (mOrderedBroadcastReceivers) {
550            // having a map separate from mOrderedBroadcastReceivers is helpful here as onReceive()
551            // call within the loop may lead to sendOrderedBroadcast() which can add to
552            // mOrderedBroadcastReceivers
553            Collection<Map.Entry<Intent, BroadcastReceiver>> map =
554                    mOrderedBroadcastReceivers.entries();
555            for (Map.Entry<Intent, BroadcastReceiver> entry : map) {
556                entry.getValue().onReceive(mContext, entry.getKey());
557                mOrderedBroadcastReceivers.remove(entry.getKey(), entry.getValue());
558            }
559        }
560    }
561
562    public void addCallingOrSelfPermission(String permission) {
563        synchronized (mPermissionTable) {
564            if (mPermissionTable != null && permission != null) {
565                mPermissionTable.remove(PERMISSION_ENABLE_ALL);
566                mPermissionTable.add(permission);
567            }
568        }
569    }
570
571    public void removeCallingOrSelfPermission(String permission) {
572        synchronized (mPermissionTable) {
573            if (mPermissionTable != null && permission != null) {
574                mPermissionTable.remove(permission);
575            }
576        }
577    }
578
579    private static void logd(String s) {
580        Log.d(TAG, s);
581    }
582}
583