1/*
2 * Copyright (C) 2015 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.server.telecom.tests;
18
19import com.google.common.collect.ArrayListMultimap;
20import com.google.common.collect.Multimap;
21
22import com.android.internal.telecom.IConnectionService;
23import com.android.internal.telecom.IInCallService;
24import com.android.server.telecom.Log;
25
26import org.mockito.MockitoAnnotations;
27import org.mockito.invocation.InvocationOnMock;
28import org.mockito.stubbing.Answer;
29
30import android.app.AppOpsManager;
31import android.app.NotificationManager;
32import android.app.StatusBarManager;
33import android.content.BroadcastReceiver;
34import android.content.ComponentName;
35import android.content.ContentResolver;
36import android.content.Context;
37import android.content.IContentProvider;
38import android.content.Intent;
39import android.content.IntentFilter;
40import android.content.ServiceConnection;
41import android.content.pm.PackageManager;
42import android.content.pm.ResolveInfo;
43import android.content.pm.ServiceInfo;
44import android.content.res.Configuration;
45import android.content.res.Resources;
46import android.media.AudioManager;
47import android.os.Bundle;
48import android.os.Handler;
49import android.os.IInterface;
50import android.os.UserHandle;
51import android.os.UserManager;
52import android.telecom.CallAudioState;
53import android.telecom.ConnectionService;
54import android.telecom.InCallService;
55import android.telecom.PhoneAccount;
56import android.telephony.SubscriptionManager;
57import android.telephony.TelephonyManager;
58import android.test.mock.MockContext;
59
60import java.io.File;
61import java.io.IOException;
62import java.util.ArrayList;
63import java.util.HashMap;
64import java.util.List;
65import java.util.Locale;
66import java.util.Map;
67
68import static org.mockito.Matchers.anyString;
69import static org.mockito.Mockito.any;
70import static org.mockito.Mockito.anyInt;
71import static org.mockito.Mockito.doAnswer;
72import static org.mockito.Mockito.eq;
73import static org.mockito.Mockito.mock;
74import static org.mockito.Mockito.spy;
75import static org.mockito.Mockito.when;
76
77/**
78 * Controls a test {@link Context} as would be provided by the Android framework to an
79 * {@code Activity}, {@code Service} or other system-instantiated component.
80 *
81 * The {@link Context} created by this object is "hollow" but its {@code applicationContext}
82 * property points to an application context implementing all the nontrivial functionality.
83 */
84public class ComponentContextFixture implements TestFixture<Context> {
85
86    public class FakeApplicationContext extends MockContext {
87        @Override
88        public PackageManager getPackageManager() {
89            return mPackageManager;
90        }
91
92        @Override
93        public File getFilesDir() {
94            try {
95                return File.createTempFile("temp", "temp").getParentFile();
96            } catch (IOException e) {
97                throw new RuntimeException(e);
98            }
99        }
100
101        @Override
102        public boolean bindServiceAsUser(
103                Intent serviceIntent,
104                ServiceConnection connection,
105                int flags,
106                UserHandle userHandle) {
107            // TODO: Implement "as user" functionality
108            return bindService(serviceIntent, connection, flags);
109        }
110
111        @Override
112        public boolean bindService(
113                Intent serviceIntent,
114                ServiceConnection connection,
115                int flags) {
116            if (mServiceByServiceConnection.containsKey(connection)) {
117                throw new RuntimeException("ServiceConnection already bound: " + connection);
118            }
119            IInterface service = mServiceByComponentName.get(serviceIntent.getComponent());
120            if (service == null) {
121                throw new RuntimeException("ServiceConnection not found: "
122                        + serviceIntent.getComponent());
123            }
124            mServiceByServiceConnection.put(connection, service);
125            connection.onServiceConnected(serviceIntent.getComponent(), service.asBinder());
126            return true;
127        }
128
129        @Override
130        public void unbindService(
131                ServiceConnection connection) {
132            IInterface service = mServiceByServiceConnection.remove(connection);
133            if (service == null) {
134                throw new RuntimeException("ServiceConnection not found: " + connection);
135            }
136            connection.onServiceDisconnected(mComponentNameByService.get(service));
137        }
138
139        @Override
140        public Object getSystemService(String name) {
141            switch (name) {
142                case Context.AUDIO_SERVICE:
143                    return mAudioManager;
144                case Context.TELEPHONY_SERVICE:
145                    return mTelephonyManager;
146                case Context.APP_OPS_SERVICE:
147                    return mAppOpsManager;
148                case Context.NOTIFICATION_SERVICE:
149                    return mNotificationManager;
150                case Context.STATUS_BAR_SERVICE:
151                    return mStatusBarManager;
152                case Context.USER_SERVICE:
153                    return mUserManager;
154                case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
155                    return mSubscriptionManager;
156                default:
157                    return null;
158            }
159        }
160
161        @Override
162        public int getUserId() {
163            return 0;
164        }
165
166        @Override
167        public Resources getResources() {
168            return mResources;
169        }
170
171        @Override
172        public String getOpPackageName() {
173            return "test";
174        }
175
176        @Override
177        public ContentResolver getContentResolver() {
178            return new ContentResolver(mApplicationContextSpy) {
179                @Override
180                protected IContentProvider acquireProvider(Context c, String name) {
181                    Log.i(this, "acquireProvider %s", name);
182                    return mock(IContentProvider.class);
183                }
184
185                @Override
186                public boolean releaseProvider(IContentProvider icp) {
187                    return true;
188                }
189
190                @Override
191                protected IContentProvider acquireUnstableProvider(Context c, String name) {
192                    Log.i(this, "acquireUnstableProvider %s", name);
193                    return mock(IContentProvider.class);
194                }
195
196                @Override
197                public boolean releaseUnstableProvider(IContentProvider icp) {
198                    return false;
199                }
200
201                @Override
202                public void unstableProviderDied(IContentProvider icp) {
203                }
204            };
205        }
206
207        @Override
208        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
209            // TODO -- this is called by WiredHeadsetManager!!!
210            return null;
211        }
212
213        @Override
214        public void sendBroadcast(Intent intent) {
215            // TODO -- need to ensure this is captured
216        }
217
218        @Override
219        public void sendBroadcast(Intent intent, String receiverPermission) {
220            // TODO -- need to ensure this is captured
221        }
222
223        @Override
224        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
225                String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
226                int initialCode, String initialData, Bundle initialExtras) {
227            // TODO -- need to ensure this is captured
228        }
229
230        @Override
231        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
232                String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
233                Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
234        }
235
236        @Override
237        public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
238                throws PackageManager.NameNotFoundException {
239            return this;
240        }
241    };
242
243    public class FakeAudioManager extends AudioManager {
244
245        private boolean mMute = false;
246        private boolean mSpeakerphoneOn = false;
247        private int mMode = AudioManager.MODE_NORMAL;
248
249        public FakeAudioManager(Context context) {
250            super(context);
251        }
252
253        @Override
254        public void setMicrophoneMute(boolean value) {
255            mMute = value;
256        }
257
258        @Override
259        public boolean isMicrophoneMute() {
260            return mMute;
261        }
262
263        @Override
264        public void setSpeakerphoneOn(boolean value) {
265            mSpeakerphoneOn = value;
266        }
267
268        @Override
269        public boolean isSpeakerphoneOn() {
270            return mSpeakerphoneOn;
271        }
272
273        @Override
274        public void setMode(int mode) {
275            mMode = mode;
276        }
277
278        @Override
279        public int getMode() {
280            return mMode;
281        }
282    }
283
284    private final Multimap<String, ComponentName> mComponentNamesByAction =
285            ArrayListMultimap.create();
286    private final Map<ComponentName, IInterface> mServiceByComponentName = new HashMap<>();
287    private final Map<ComponentName, ServiceInfo> mServiceInfoByComponentName = new HashMap<>();
288    private final Map<IInterface, ComponentName> mComponentNameByService = new HashMap<>();
289    private final Map<ServiceConnection, IInterface> mServiceByServiceConnection = new HashMap<>();
290
291    private final Context mContext = new MockContext() {
292        @Override
293        public Context getApplicationContext() {
294            return mApplicationContextSpy;
295        }
296
297        @Override
298        public Resources getResources() {
299            return mResources;
300        }
301    };
302
303    // The application context is the most important object this class provides to the system
304    // under test.
305    private final Context mApplicationContext = new FakeApplicationContext();
306
307    // We then create a spy on the application context allowing standard Mockito-style
308    // when(...) logic to be used to add specific little responses where needed.
309
310    private final Resources mResources = mock(Resources.class);
311    private final Context mApplicationContextSpy = spy(mApplicationContext);
312    private final PackageManager mPackageManager = mock(PackageManager.class);
313    private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext));
314    private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
315    private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
316    private final NotificationManager mNotificationManager = mock(NotificationManager.class);
317    private final UserManager mUserManager = mock(UserManager.class);
318    private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
319    private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
320    private final Configuration mResourceConfiguration = new Configuration();
321
322    public ComponentContextFixture() {
323        MockitoAnnotations.initMocks(this);
324        when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
325        mResourceConfiguration.setLocale(Locale.TAIWAN);
326
327        // TODO: Move into actual tests
328        when(mAudioManager.isWiredHeadsetOn()).thenReturn(false);
329
330        doAnswer(new Answer<List<ResolveInfo>>() {
331            @Override
332            public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
333                return doQueryIntentServices(
334                        (Intent) invocation.getArguments()[0],
335                        (Integer) invocation.getArguments()[1]);
336            }
337        }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt());
338
339        doAnswer(new Answer<List<ResolveInfo>>() {
340            @Override
341            public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
342                return doQueryIntentServices(
343                        (Intent) invocation.getArguments()[0],
344                        (Integer) invocation.getArguments()[1]);
345            }
346        }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
347
348        when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1);
349
350        doAnswer(new Answer<Void>(){
351            @Override
352            public Void answer(InvocationOnMock invocation) throws Throwable {
353                return null;
354            }
355        }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
356
357        when(mNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
358
359        when(mUserManager.getSerialNumberForUser(any(UserHandle.class))).thenReturn(-1L);
360    }
361
362    @Override
363    public Context getTestDouble() {
364        return mContext;
365    }
366
367    public void addConnectionService(
368            ComponentName componentName,
369            IConnectionService service)
370            throws Exception {
371        addService(ConnectionService.SERVICE_INTERFACE, componentName, service);
372        ServiceInfo serviceInfo = new ServiceInfo();
373        serviceInfo.permission = android.Manifest.permission.BIND_CONNECTION_SERVICE;
374        serviceInfo.packageName = componentName.getPackageName();
375        serviceInfo.name = componentName.getClassName();
376        mServiceInfoByComponentName.put(componentName, serviceInfo);
377    }
378
379    public void addInCallService(
380            ComponentName componentName,
381            IInCallService service)
382            throws Exception {
383        addService(InCallService.SERVICE_INTERFACE, componentName, service);
384        ServiceInfo serviceInfo = new ServiceInfo();
385        serviceInfo.permission = android.Manifest.permission.BIND_INCALL_SERVICE;
386        serviceInfo.packageName = componentName.getPackageName();
387        serviceInfo.name = componentName.getClassName();
388        mServiceInfoByComponentName.put(componentName, serviceInfo);
389    }
390
391    public void putResource(int id, String value) {
392        when(mResources.getString(eq(id))).thenReturn(value);
393    }
394
395    private void addService(String action, ComponentName name, IInterface service) {
396        mComponentNamesByAction.put(action, name);
397        mServiceByComponentName.put(name, service);
398        mComponentNameByService.put(service, name);
399    }
400
401    private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
402        List<ResolveInfo> result = new ArrayList<>();
403        for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
404            ResolveInfo resolveInfo = new ResolveInfo();
405            resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
406            result.add(resolveInfo);
407        }
408        return result;
409    }
410}
411