/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.MatrixCursor; import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; import android.os.PersistableBundle; import android.os.UserHandle; import android.os.UserManager; import android.preference.PreferenceManager; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** * Controls a test {@link Context} as would be provided by the Android framework to an * {@code Activity}, {@code Service} or other system-instantiated component. * * Contains Fake classes like FakeContext for components that require complex and * reusable stubbing. Others can be mocked using Mockito functions in tests or constructor/public * methods of this class. */ public class ContextFixture implements TestFixture { private static final String TAG = "ContextFixture"; public static final String PERMISSION_ENABLE_ALL = "android.permission.STUB_PERMISSION"; public class FakeContentProvider extends MockContentProvider { private String[] mColumns = {"name", "value"}; private HashMap mKeyValuePairs = new HashMap(); private int mNumKeyValuePairs = 0; @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { Uri newUri = null; if (values != null) { mKeyValuePairs.put(values.getAsString("name"), values.getAsString("value")); mNumKeyValuePairs++; newUri = Uri.withAppendedPath(uri, "" + mNumKeyValuePairs); } logd("insert called, new mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri + " newUri: " + newUri); return newUri; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //assuming query will always be of the form 'name = ?' logd("query called, mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri); if (mKeyValuePairs.containsKey(selectionArgs[0])) { MatrixCursor cursor = new MatrixCursor(projection); cursor.addRow(new String[]{mKeyValuePairs.get(selectionArgs[0])}); return cursor; } return null; } @Override public Bundle call(String method, String request, Bundle args) { logd("call called, mNumKeyValuePairs: " + mNumKeyValuePairs + " method: " + method + " request: " + request); switch(method) { case Settings.CALL_METHOD_GET_GLOBAL: case Settings.CALL_METHOD_GET_SECURE: case Settings.CALL_METHOD_GET_SYSTEM: if (mKeyValuePairs.containsKey(request)) { Bundle b = new Bundle(1); b.putCharSequence("value", mKeyValuePairs.get(request)); logd("returning value pair: " + mKeyValuePairs.get(request) + " for " + request); return b; } break; case Settings.CALL_METHOD_PUT_GLOBAL: case Settings.CALL_METHOD_PUT_SECURE: case Settings.CALL_METHOD_PUT_SYSTEM: logd("adding key-value pair: " + request + "-" + (String)args.get("value")); mKeyValuePairs.put(request, (String)args.get("value")); mNumKeyValuePairs++; break; } return null; } } private final HashMap mSystemServices = new HashMap(); public void setSystemService(String name, Object service) { synchronized (mSystemServices) { mSystemServices.put(name, service); } } public class FakeContext extends MockContext { @Override public PackageManager getPackageManager() { return mPackageManager; } @Override public boolean bindService( Intent serviceIntent, ServiceConnection connection, int flags) { if (mServiceByServiceConnection.containsKey(connection)) { throw new RuntimeException("ServiceConnection already bound: " + connection); } IInterface service = mServiceByComponentName.get(serviceIntent.getComponent()); if (service == null) { throw new RuntimeException("ServiceConnection not found: " + serviceIntent.getComponent()); } mServiceByServiceConnection.put(connection, service); connection.onServiceConnected(serviceIntent.getComponent(), service.asBinder()); return true; } @Override public void unbindService( ServiceConnection connection) { IInterface service = mServiceByServiceConnection.remove(connection); if (service == null) { throw new RuntimeException("ServiceConnection not found: " + connection); } connection.onServiceDisconnected(mComponentNameByService.get(service)); } @Override public Object getSystemService(String name) { synchronized (mSystemServices) { Object service = mSystemServices.get(name); if (service != null) return service; } switch (name) { case Context.TELEPHONY_SERVICE: return mTelephonyManager; case Context.APP_OPS_SERVICE: return mAppOpsManager; case Context.NOTIFICATION_SERVICE: return mNotificationManager; case Context.USER_SERVICE: return mUserManager; case Context.CARRIER_CONFIG_SERVICE: return mCarrierConfigManager; case Context.POWER_SERVICE: // PowerManager is a final class so cannot be mocked, return real service return TestApplication.getAppContext().getSystemService(name); case Context.TELEPHONY_SUBSCRIPTION_SERVICE: return mSubscriptionManager; case Context.WIFI_SERVICE: return mWifiManager; case Context.ALARM_SERVICE: return mAlarmManager; case Context.CONNECTIVITY_SERVICE: return mConnectivityManager; case Context.USAGE_STATS_SERVICE: return mUsageStatManager; default: return null; } } @Override public int getUserId() { return 0; } @Override public Resources getResources() { return mResources; } @Override public String getOpPackageName() { return "com.android.internal.telephony"; } @Override public ContentResolver getContentResolver() { return mContentResolver; } @Override public void unregisterReceiver(BroadcastReceiver receiver) { } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { return registerReceiver(receiver, filter, null, null); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { return registerReceiverAsUser(receiver, null, filter, broadcastPermission, scheduler); } @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { Intent result = null; synchronized (mBroadcastReceiversByAction) { for (int i = 0 ; i < filter.countActions() ; i++) { mBroadcastReceiversByAction.put(filter.getAction(i), receiver); if (result == null) { result = mStickyBroadcastByAction.get(filter.getAction(i)); } } } return result; } @Override public void sendBroadcast(Intent intent) { logd("sendBroadcast called for " + intent.getAction()); synchronized (mBroadcastReceiversByAction) { for (BroadcastReceiver broadcastReceiver : mBroadcastReceiversByAction.get(intent.getAction())) { broadcastReceiver.onReceive(mContext, intent); } } } @Override public void sendBroadcast(Intent intent, String receiverPermission) { logd("sendBroadcast called for " + intent.getAction()); sendBroadcast(intent); } @Override public void sendBroadcastAsUser(Intent intent, UserHandle user) { sendBroadcast(intent); } @Override public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { sendBroadcast(intent); } @Override public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp) { sendBroadcast(intent); } @Override public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { logd("sendOrderedBroadcastAsUser called for " + intent.getAction()); sendBroadcast(intent); if (resultReceiver != null) { synchronized (mOrderedBroadcastReceivers) { mOrderedBroadcastReceivers.put(intent, resultReceiver); } } } @Override public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { logd("sendOrderedBroadcastAsUser called for " + intent.getAction()); sendBroadcast(intent); if (resultReceiver != null) { synchronized (mOrderedBroadcastReceivers) { mOrderedBroadcastReceivers.put(intent, resultReceiver); } } } @Override public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { logd("sendOrderedBroadcastAsUser called for " + intent.getAction()); sendBroadcast(intent); if (resultReceiver != null) { synchronized (mOrderedBroadcastReceivers) { mOrderedBroadcastReceivers.put(intent, resultReceiver); } } } @Override public void sendStickyBroadcast(Intent intent) { logd("sendStickyBroadcast called for " + intent.getAction()); synchronized (mBroadcastReceiversByAction) { sendBroadcast(intent); mStickyBroadcastByAction.put(intent.getAction(), intent); } } @Override public void sendStickyBroadcastAsUser(Intent intent, UserHandle ignored) { logd("sendStickyBroadcastAsUser called for " + intent.getAction()); sendStickyBroadcast(intent); } @Override public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws PackageManager.NameNotFoundException { return this; } @Override public void enforceCallingOrSelfPermission(String permission, String message) { if (mPermissionTable.contains(permission) || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) { return; } logd("requested permission: " + permission + " got denied"); throw new SecurityException(permission + " denied: " + message); } @Override public int checkCallingOrSelfPermission(String permission) { return PackageManager.PERMISSION_GRANTED; } @Override public SharedPreferences getSharedPreferences(String name, int mode) { return mSharedPreferences; } @Override public String getPackageName() { return "com.android.internal.telephony"; } public boolean testMethod() { return true; } public boolean testMethod1() { return true; } public boolean testMethod2() { return true; } } private final Multimap mComponentNamesByAction = ArrayListMultimap.create(); private final Map mServiceByComponentName = new HashMap(); private final Map mServiceInfoByComponentName = new HashMap(); private final Map mComponentNameByService = new HashMap(); private final Map mServiceByServiceConnection = new HashMap(); private final Multimap mBroadcastReceiversByAction = ArrayListMultimap.create(); private final HashMap mStickyBroadcastByAction = new HashMap(); private final Multimap mOrderedBroadcastReceivers = ArrayListMultimap.create(); private final HashSet mPermissionTable = new HashSet<>(); // The application context is the most important object this class provides to the system // under test. private final Context mContext = spy(new FakeContext()); // We then create a spy on the application context allowing standard Mockito-style // when(...) logic to be used to add specific little responses where needed. private final Resources mResources = mock(Resources.class); private final PackageManager mPackageManager = mock(PackageManager.class); private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class); private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class); private final NotificationManager mNotificationManager = mock(NotificationManager.class); private final UserManager mUserManager = mock(UserManager.class); private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class); private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class); private final AlarmManager mAlarmManager = mock(AlarmManager.class); private final ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class); private final UsageStatsManager mUsageStatManager = null; private final WifiManager mWifiManager = mock(WifiManager.class); private final ContentProvider mContentProvider = spy(new FakeContentProvider()); private final Configuration mConfiguration = new Configuration(); private final SharedPreferences mSharedPreferences = PreferenceManager .getDefaultSharedPreferences(TestApplication.getAppContext()); private final MockContentResolver mContentResolver = new MockContentResolver(); private final PersistableBundle mBundle = new PersistableBundle(); public ContextFixture() { MockitoAnnotations.initMocks(this); doAnswer(new Answer>() { @Override public List answer(InvocationOnMock invocation) throws Throwable { return doQueryIntentServices( (Intent) invocation.getArguments()[0], (Integer) invocation.getArguments()[1]); } }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt()); doAnswer(new Answer>() { @Override public List answer(InvocationOnMock invocation) throws Throwable { return doQueryIntentServices( (Intent) invocation.getArguments()[0], (Integer) invocation.getArguments()[1]); } }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt()); doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt()); mConfiguration.locale = Locale.US; doReturn(mConfiguration).when(mResources).getConfiguration(); mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider); mPermissionTable.add(PERMISSION_ENABLE_ALL); } @Override public Context getTestDouble() { return mContext; } public void putResource(int id, final String value) { when(mResources.getText(eq(id))).thenReturn(value); when(mResources.getString(eq(id))).thenReturn(value); when(mResources.getString(eq(id), any())).thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); return String.format(value, Arrays.copyOfRange(args, 1, args.length)); } }); } public void putBooleanResource(int id, boolean value) { when(mResources.getBoolean(eq(id))).thenReturn(value); } public void putStringArrayResource(int id, String[] values) { doReturn(values).when(mResources).getStringArray(eq(id)); } public void putIntArrayResource(int id, int[] values) { doReturn(values).when(mResources).getIntArray(eq(id)); } public PersistableBundle getCarrierConfigBundle() { return mBundle; } private void addService(String action, ComponentName name, IInterface service) { mComponentNamesByAction.put(action, name); mServiceByComponentName.put(name, service); mComponentNameByService.put(service, name); } private List doQueryIntentServices(Intent intent, int flags) { List result = new ArrayList(); for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName); result.add(resolveInfo); } return result; } public void sendBroadcastToOrderedBroadcastReceivers() { synchronized (mOrderedBroadcastReceivers) { // having a map separate from mOrderedBroadcastReceivers is helpful here as onReceive() // call within the loop may lead to sendOrderedBroadcast() which can add to // mOrderedBroadcastReceivers Collection> map = mOrderedBroadcastReceivers.entries(); for (Map.Entry entry : map) { entry.getValue().onReceive(mContext, entry.getKey()); mOrderedBroadcastReceivers.remove(entry.getKey(), entry.getValue()); } } } public void addCallingOrSelfPermission(String permission) { synchronized (mPermissionTable) { if (mPermissionTable != null && permission != null) { mPermissionTable.remove(PERMISSION_ENABLE_ALL); mPermissionTable.add(permission); } } } public void removeCallingOrSelfPermission(String permission) { synchronized (mPermissionTable) { if (mPermissionTable != null && permission != null) { mPermissionTable.remove(permission); } } } private static void logd(String s) { Log.d(TAG, s); } }