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