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 
17 
18 package com.android.cts.managedprofile;
19 
20 import static com.android.cts.managedprofile.DummyConnectionService.MISSED_PHONE_NUMBER;
21 import static com.android.cts.managedprofile.DummyConnectionService.NORMAL_PHONE_NUMBER;
22 
23 import android.app.Instrumentation;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.database.ContentObserver;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.HandlerThread;
33 import android.os.ParcelFileDescriptor;
34 import android.os.Process;
35 import android.os.UserManager;
36 import android.provider.CallLog.Calls;
37 import android.telecom.PhoneAccount;
38 import android.telecom.PhoneAccountHandle;
39 import android.telecom.TelecomManager;
40 import android.test.InstrumentationTestCase;
41 
42 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
43 import com.android.cts.managedprofile.MissedCallNotificationReceiver.IntentListener;
44 import java.io.BufferedReader;
45 import java.io.FileInputStream;
46 import java.io.InputStream;
47 import java.io.InputStreamReader;
48 import java.nio.charset.StandardCharsets;
49 import java.util.Arrays;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.LinkedBlockingQueue;
52 import java.util.concurrent.TimeUnit;
53 
54 public class PhoneAccountTest extends InstrumentationTestCase {
55 
56     static final String CALL_PROVIDER_ID = "testapps_TestConnectionService_CALL_PROVIDER_ID";
57 
58     private static final String COMMAND_ENABLE = "telecom set-phone-account-enabled";
59 
60     private static final String QUERY_CALL_THROUGH_OUR_CONNECTION_SERVICE = Calls.NUMBER
61             + " = ? AND " + Calls.PHONE_ACCOUNT_COMPONENT_NAME + " = ?";
62 
63     private TelecomManager mTelecomManager;
64 
65     private Context mContext;
66 
67     private Instrumentation mInstrumentation;
68 
69     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
70 
71     private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
72             new ComponentName(MANAGED_PROFILE_PKG,
73                     DummyConnectionService.class.getName()), CALL_PROVIDER_ID,
74             Process.myUserHandle());
75 
76     @Override
setUp()77     protected void setUp() throws Exception {
78         super.setUp();
79         mInstrumentation = getInstrumentation();
80         mContext = getInstrumentation().getContext();
81         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
82     }
83 
84     @Override
tearDown()85     protected void tearDown() throws Exception {
86         super.tearDown();
87     }
88 
testOutgoingCallUsingTelecomManager()89     public void testOutgoingCallUsingTelecomManager() throws Exception {
90         internalTestOutgoingCall(true /* usingTelecomManager */);
91     }
92 
testOutgoingCallUsingActionCall()93     public void testOutgoingCallUsingActionCall() throws Exception {
94         internalTestOutgoingCall(false /* usingTelecomManager */);
95     }
96 
97     /**
98      *  Placing an outgoing call through our phone account and verify the call is inserted
99      *  properly.
100      */
internalTestOutgoingCall(boolean usingTelecomManager)101     private void internalTestOutgoingCall(boolean usingTelecomManager) throws Exception {
102         final String phoneNumber = NORMAL_PHONE_NUMBER;
103         // Make sure no lingering values from previous runs.
104         cleanupCall(phoneNumber, false /*verifyDeletion*/);
105         final Context context = getInstrumentation().getContext();
106         final HandlerThread handlerThread = new HandlerThread("Observer");
107         // Register the phone account.
108         final PhoneAccountHandle phoneAccountHandle = registerPhoneAccount().getAccountHandle();
109         try {
110             // Register the ContentObserver so that we will be get notified when the call is
111             // inserted.
112             final CountDownLatch countDownLatch = new CountDownLatch(1);
113             handlerThread.start();
114             context.getContentResolver().registerContentObserver(Calls.CONTENT_URI, false,
115                     new CalllogContentObserver(new Handler(handlerThread.getLooper()),
116                             countDownLatch));
117 
118             // Place the call.
119             if (usingTelecomManager) {
120                 placeCallUsingTelecomManager(phoneAccountHandle, phoneNumber);
121             } else {
122                 placeCallUsingActionCall(phoneAccountHandle, phoneNumber);
123             }
124 
125             // Make sure the call inserted is correct.
126             boolean calllogProviderChanged = countDownLatch.await(1, TimeUnit.MINUTES);
127             assertTrue(calllogProviderChanged);
128             assertCalllogInserted(Calls.OUTGOING_TYPE, phoneNumber);
129         } finally {
130             handlerThread.quit();
131             cleanupCall(phoneNumber, true /* verifyDeletion */ );
132             unregisterPhoneAccount();
133         }
134     }
135 
placeCallUsingTelecomManager( PhoneAccountHandle phoneAccountHandle, String phoneNumber)136     private void placeCallUsingTelecomManager(
137         PhoneAccountHandle phoneAccountHandle, String phoneNumber) {
138         Uri phoneUri = Uri.fromParts(
139             PhoneAccount.SCHEME_TEL, phoneNumber, null);
140         Bundle extras = new Bundle();
141         extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
142         mTelecomManager.placeCall(phoneUri, extras);
143     }
144 
placeCallUsingActionCall( PhoneAccountHandle phoneAccountHandle, String phoneNumber)145     private void placeCallUsingActionCall(
146         PhoneAccountHandle phoneAccountHandle, String phoneNumber) {
147         Intent intent = new Intent(Intent.ACTION_CALL);
148         intent.setData(Uri.parse("tel:" + phoneNumber));
149         intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
150         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
151         mContext.startActivity(intent);
152     }
153 
154     /**
155      *  Add an incoming call with our phone account and verify the call is inserted properly.
156      */
testIncomingCall()157     public void testIncomingCall() throws Exception {
158         internalTestIncomingCall(false /* missedCall */);
159     }
160 
161     /**
162      *  Add an missed incoming call with our phone account and verify the call is inserted properly.
163      */
testIncomingMissedCall()164     public void testIncomingMissedCall() throws Exception {
165         final LinkedBlockingQueue<Intent> queue = new LinkedBlockingQueue<>(1);
166         MissedCallNotificationReceiver.setIntentListener(new IntentListener() {
167             @Override
168             public void onIntentReceived(Intent intent) {
169                 queue.offer(intent);
170             }
171         });
172         internalTestIncomingCall(true /* missedCall */);
173         Intent intent = queue.poll(10, TimeUnit.SECONDS);
174         assertNotNull(intent);
175         assertEquals(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, intent.getAction());
176         assertEquals(
177                 MISSED_PHONE_NUMBER,
178                 intent.getStringExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER));
179     }
180 
internalTestIncomingCall(boolean missedCall)181     private void internalTestIncomingCall(boolean missedCall) throws Exception {
182         final String phoneNumber = missedCall ? MISSED_PHONE_NUMBER : NORMAL_PHONE_NUMBER;
183         final int callType = missedCall ? Calls.MISSED_TYPE : Calls.INCOMING_TYPE;
184         // Make sure no lingering values from previous runs.
185         cleanupCall(phoneNumber, false /* verifyDeletion */ );
186         final Context context = getInstrumentation().getContext();
187         final HandlerThread handlerThread = new HandlerThread("Observer");
188         // Register the phone account.
189         final PhoneAccountHandle phoneAccountHandle = registerPhoneAccount().getAccountHandle();
190         try {
191             // Register the ContentObserver so that we will be get notified when the call is
192             // inserted.
193             final CountDownLatch countDownLatch = new CountDownLatch(1);
194             handlerThread.start();
195             context.getContentResolver().registerContentObserver(Calls.CONTENT_URI, false,
196                 new CalllogContentObserver(new Handler(handlerThread.getLooper()),
197                     countDownLatch));
198 
199             // Add a incoming call.
200             final Bundle bundle = new Bundle();
201             final Uri phoneUri = Uri.fromParts(
202                 PhoneAccount.SCHEME_TEL, phoneNumber, null);
203             bundle.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, phoneUri);
204 
205             mTelecomManager.addNewIncomingCall(phoneAccountHandle, bundle);
206 
207             // Make sure the call inserted is correct.
208             boolean calllogProviderChanged = countDownLatch.await(1, TimeUnit.MINUTES);
209             assertTrue(calllogProviderChanged);
210             assertCalllogInserted(callType, phoneNumber);
211         } finally {
212             handlerThread.quit();
213             cleanupCall(phoneNumber, true /* verifyDeletion */ );
214             unregisterPhoneAccount();
215         }
216     }
217 
testEnsureCallNotInserted()218     public void testEnsureCallNotInserted() {
219         Cursor cursor = null;
220         try {
221             cursor = mContext.getContentResolver()
222                     .query(Calls.CONTENT_URI, null, Calls.NUMBER + " in (?,?)",
223                             new String[]{NORMAL_PHONE_NUMBER, MISSED_PHONE_NUMBER}, null);
224             assertEquals(0, cursor.getCount());
225         } finally {
226             if (cursor != null) {
227                 cursor.close();
228             }
229         }
230     }
231 
testRegisterPhoneAccount()232     public void testRegisterPhoneAccount() throws Exception {
233         registerPhoneAccount();
234     }
235 
testUnregisterPhoneAccount()236     public void testUnregisterPhoneAccount() {
237         unregisterPhoneAccount();
238     }
239 
testPhoneAccountNotRegistered()240     public void testPhoneAccountNotRegistered() {
241         assertNull(mTelecomManager.getPhoneAccount(PHONE_ACCOUNT_HANDLE));
242     }
243 
assertCalllogInserted(int type, String phoneNumber)244     private void assertCalllogInserted(int type, String phoneNumber) {
245         Cursor cursor = null;
246         try {
247             final String connectionServiceComponentName = new ComponentName(mContext,
248                 DummyConnectionService.class).flattenToString();
249             cursor = mContext.getContentResolver()
250                 .query(Calls.CONTENT_URI, null,
251                     QUERY_CALL_THROUGH_OUR_CONNECTION_SERVICE + " AND " +
252                         Calls.TYPE + " = ?",
253                     new String[]{
254                         phoneNumber,
255                         connectionServiceComponentName,
256                         String.valueOf(type)
257                     },
258                     null);
259             assertEquals(1, cursor.getCount());
260         } finally {
261             if (cursor != null) {
262                 cursor.close();
263             }
264         }
265     }
266 
cleanupCall(String phoneNumber, boolean verifyDeletion)267     private void cleanupCall(String phoneNumber, boolean verifyDeletion) {
268         final String connectionServiceComponentName = new ComponentName(mContext,
269                 DummyConnectionService.class).flattenToString();
270         int numRowDeleted = mContext.getContentResolver()
271                 .delete(Calls.CONTENT_URI, QUERY_CALL_THROUGH_OUR_CONNECTION_SERVICE,
272                         new String[]{phoneNumber, connectionServiceComponentName});
273         if (verifyDeletion) {
274             assertEquals(1, numRowDeleted);
275         }
276     }
277 
registerPhoneAccount()278     private PhoneAccount registerPhoneAccount() throws Exception {
279         final PhoneAccount phoneAccount = PhoneAccount.builder(
280                 PHONE_ACCOUNT_HANDLE,
281                 "TelecomTestApp Call Provider")
282                 .setAddress(Uri.parse("tel:555-TEST"))
283                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
284                 .setShortDescription("a short description for the call provider")
285                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
286                 .build();
287         mTelecomManager.registerPhoneAccount(phoneAccount);
288         enablePhoneAccount(PHONE_ACCOUNT_HANDLE);
289         // make sure the registration is successful.
290         assertNotNull(mTelecomManager.getPhoneAccount(PHONE_ACCOUNT_HANDLE));
291         return phoneAccount;
292     }
293 
unregisterPhoneAccount()294     private void unregisterPhoneAccount() {
295         mTelecomManager.unregisterPhoneAccount(PHONE_ACCOUNT_HANDLE);
296         assertNull(mTelecomManager.getPhoneAccount(PHONE_ACCOUNT_HANDLE));
297     }
298 
299     /**
300      * Running adb command to enable phone account.
301      */
enablePhoneAccount(PhoneAccountHandle handle)302     private void enablePhoneAccount(PhoneAccountHandle handle) throws Exception {
303         final ComponentName component = handle.getComponentName();
304         final UserManager userManager = (UserManager) mContext.getSystemService(
305                 Context.USER_SERVICE);
306         executeShellCommand(COMMAND_ENABLE + " "
307                 + component.getPackageName() + "/" + component.getClassName() + " "
308                 + handle.getId() + " " + userManager
309                 .getSerialNumberForUser(Process.myUserHandle()));
310     }
311 
executeShellCommand(String command)312     private String executeShellCommand(String command) throws Exception {
313         final ParcelFileDescriptor pfd =
314                 mInstrumentation.getUiAutomation().executeShellCommand(command);
315         BufferedReader br = null;
316         try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
317             br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
318             String str;
319             StringBuilder out = new StringBuilder();
320             while ((str = br.readLine()) != null) {
321                 out.append(str);
322             }
323             return out.toString();
324         } finally {
325             if (br != null) {
326                 br.close();
327             }
328             pfd.close();
329         }
330     }
331 
332     /**
333      * Observe the change of calllog provider.
334      */
335     private class CalllogContentObserver extends ContentObserver {
336 
337         private final CountDownLatch mCountDownLatch;
338 
CalllogContentObserver(Handler handler, final CountDownLatch countDownLatch)339         public CalllogContentObserver(Handler handler, final CountDownLatch countDownLatch) {
340             super(handler);
341             mCountDownLatch = countDownLatch;
342         }
343 
344         @Override
onChange(boolean selfChange, Uri uri)345         public void onChange(boolean selfChange, Uri uri) {
346             super.onChange(selfChange, uri);
347             mCountDownLatch.countDown();
348         }
349     }
350 }
351