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 package android.telecom.cts; 17 18 import android.app.Instrumentation; 19 import android.bluetooth.BluetoothDevice; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.graphics.Color; 24 import android.net.Uri; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Parcel; 30 import android.os.ParcelFileDescriptor; 31 import android.os.SystemClock; 32 import android.support.test.InstrumentationRegistry; 33 import android.telecom.PhoneAccount; 34 import android.telecom.PhoneAccountHandle; 35 import android.telecom.TelecomManager; 36 37 import junit.framework.TestCase; 38 39 import java.io.BufferedReader; 40 import java.io.FileInputStream; 41 import java.io.InputStream; 42 import java.io.InputStreamReader; 43 import java.nio.charset.StandardCharsets; 44 import java.util.ArrayList; 45 import java.util.Optional; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.TimeUnit; 48 import java.util.function.Predicate; 49 50 public class TestUtils { 51 static final String TAG = "TelecomCTSTests"; 52 static final boolean HAS_TELECOM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 53 static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000; 54 static final long WAIT_FOR_CALL_ADDED_TIMEOUT_S = 15; 55 static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_CALLBACK = 50; 56 static final long WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S = 15; 57 static final long WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S = 15; 58 static final boolean HAS_BLUETOOTH = hasBluetoothFeature(); 59 static final BluetoothDevice BLUETOOTH_DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01"); 60 static final BluetoothDevice BLUETOOTH_DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02"); 61 62 // Non-final to allow modification by tests not in this package (e.g. permission-related 63 // tests in the Telecom2 test package. 64 public static String PACKAGE = "android.telecom.cts"; 65 public static final String COMPONENT = "android.telecom.cts.CtsConnectionService"; 66 public static final String SELF_MANAGED_COMPONENT = 67 "android.telecom.cts.CtsSelfManagedConnectionService"; 68 public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService"; 69 public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID"; 70 public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE = 71 new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID); 72 public static final PhoneAccountHandle TEST_HANDOVER_SRC_PHONE_ACCOUNT_HANDLE = 73 new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), "handoverFrom"); 74 public static final PhoneAccountHandle TEST_HANDOVER_DEST_PHONE_ACCOUNT_HANDLE = 75 new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), 76 "handoverTo"); 77 public static final String REMOTE_ACCOUNT_ID = "xtstest_REMOTE_CALL_PROVIDER_ID"; 78 public static final String SELF_MANAGED_ACCOUNT_ID_1 = "ctstest_SELF_MANAGED_ID_1"; 79 public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_1 = 80 new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), 81 SELF_MANAGED_ACCOUNT_ID_1); 82 public static final String SELF_MANAGED_ACCOUNT_ID_2 = "ctstest_SELF_MANAGED_ID_2"; 83 public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_2 = 84 new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), 85 SELF_MANAGED_ACCOUNT_ID_2); 86 public static final String SELF_MANAGED_ACCOUNT_ID_3 = "ctstest_SELF_MANAGED_ID_3"; 87 public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_3 = 88 new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), 89 SELF_MANAGED_ACCOUNT_ID_3); 90 91 public static final String ACCOUNT_LABEL = "CTSConnectionService"; 92 public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder( 93 TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL) 94 .setAddress(Uri.parse("tel:555-TEST")) 95 .setSubscriptionAddress(Uri.parse("tel:555-TEST")) 96 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER | 97 PhoneAccount.CAPABILITY_VIDEO_CALLING | 98 PhoneAccount.CAPABILITY_RTT | 99 PhoneAccount.CAPABILITY_CONNECTION_MANAGER) 100 .setHighlightColor(Color.RED) 101 .setShortDescription(ACCOUNT_LABEL) 102 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) 103 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) 104 .build(); 105 private static final Bundle SUPPORTS_HANDOVER_FROM_EXTRAS = new Bundle(); 106 private static final Bundle SUPPORTS_HANDOVER_TO_EXTRAS = new Bundle(); 107 static { 108 SUPPORTS_HANDOVER_FROM_EXTRAS.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_FROM, true); 109 SUPPORTS_HANDOVER_TO_EXTRAS.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true); 110 } 111 public static final PhoneAccount TEST_PHONE_ACCOUNT_HANDOVER_SRC = PhoneAccount.builder( 112 TEST_HANDOVER_SRC_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL) 113 .setAddress(Uri.parse("tel:555-TEST")) 114 .setExtras(SUPPORTS_HANDOVER_FROM_EXTRAS) 115 .setSubscriptionAddress(Uri.parse("tel:555-TEST")) 116 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) 117 .setHighlightColor(Color.BLUE) 118 .setShortDescription(ACCOUNT_LABEL) 119 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) 120 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) 121 .build(); 122 public static final PhoneAccount TEST_PHONE_ACCOUNT_HANDOVER_DEST = PhoneAccount.builder( 123 TEST_HANDOVER_DEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL) 124 .setAddress(Uri.parse("tel:555-TEST")) 125 .setExtras(SUPPORTS_HANDOVER_TO_EXTRAS) 126 .setSubscriptionAddress(Uri.parse("tel:555-TEST")) 127 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) 128 .setHighlightColor(Color.MAGENTA) 129 .setShortDescription(ACCOUNT_LABEL) 130 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) 131 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) 132 .build(); 133 public static final String REMOTE_ACCOUNT_LABEL = "CTSRemoteConnectionService"; 134 public static final String SELF_MANAGED_ACCOUNT_LABEL = "android.telecom.cts"; 135 public static final String TEST_URI_SCHEME = "foobuzz"; 136 public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_3 = PhoneAccount.builder( 137 TEST_SELF_MANAGED_HANDLE_3, SELF_MANAGED_ACCOUNT_LABEL) 138 .setAddress(Uri.fromParts(TEST_URI_SCHEME, "test (at) test.com", null)) 139 .setSubscriptionAddress(Uri.fromParts(TEST_URI_SCHEME, "test (at) test.com", null)) 140 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | 141 PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING | 142 PhoneAccount.CAPABILITY_VIDEO_CALLING) 143 .setHighlightColor(Color.BLUE) 144 .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL) 145 .addSupportedUriScheme(TEST_URI_SCHEME) 146 .build(); 147 public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = PhoneAccount.builder( 148 TEST_SELF_MANAGED_HANDLE_2, SELF_MANAGED_ACCOUNT_LABEL) 149 .setAddress(Uri.parse("sip:test (at) test.com")) 150 .setSubscriptionAddress(Uri.parse("sip:test (at) test.com")) 151 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | 152 PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING | 153 PhoneAccount.CAPABILITY_VIDEO_CALLING) 154 .setHighlightColor(Color.BLUE) 155 .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL) 156 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) 157 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP) 158 .build(); 159 public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_1 = PhoneAccount.builder( 160 TEST_SELF_MANAGED_HANDLE_1, SELF_MANAGED_ACCOUNT_LABEL) 161 .setAddress(Uri.parse("sip:test (at) test.com")) 162 .setSubscriptionAddress(Uri.parse("sip:test (at) test.com")) 163 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | 164 PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING | 165 PhoneAccount.CAPABILITY_VIDEO_CALLING) 166 .setHighlightColor(Color.BLUE) 167 .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL) 168 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) 169 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP) 170 .build(); 171 172 private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer "; 173 174 private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer"; 175 176 private static final String COMMAND_GET_SYSTEM_DIALER = "telecom get-system-dialer"; 177 178 private static final String COMMAND_ENABLE = "telecom set-phone-account-enabled "; 179 180 private static final String COMMAND_REGISTER_SIM = "telecom register-sim-phone-account "; 181 182 private static final String COMMAND_WAIT_ON_HANDLERS = "telecom wait-on-handlers"; 183 184 public static final String MERGE_CALLER_NAME = "calls-merged"; 185 public static final String SWAP_CALLER_NAME = "calls-swapped"; 186 private static final String PRIMARY_USER_SN = "0"; 187 188 public static boolean shouldTestTelecom(Context context) { 189 if (!HAS_TELECOM) { 190 return false; 191 } 192 final PackageManager pm = context.getPackageManager(); 193 return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && 194 pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE); 195 } 196 197 public static String setDefaultDialer(Instrumentation instrumentation, String packageName) 198 throws Exception { 199 return executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER + packageName); 200 } 201 202 public static String getDefaultDialer(Instrumentation instrumentation) throws Exception { 203 return executeShellCommand(instrumentation, COMMAND_GET_DEFAULT_DIALER); 204 } 205 206 public static String getSystemDialer(Instrumentation instrumentation) throws Exception { 207 return executeShellCommand(instrumentation, COMMAND_GET_SYSTEM_DIALER); 208 } 209 210 public static void enablePhoneAccount(Instrumentation instrumentation, 211 PhoneAccountHandle handle) throws Exception { 212 final ComponentName component = handle.getComponentName(); 213 executeShellCommand(instrumentation, COMMAND_ENABLE 214 + component.getPackageName() + "/" + component.getClassName() + " " 215 + handle.getId() + " " + PRIMARY_USER_SN); 216 } 217 218 public static void registerSimPhoneAccount(Instrumentation instrumentation, 219 PhoneAccountHandle handle, String label, String address) throws Exception { 220 final ComponentName component = handle.getComponentName(); 221 executeShellCommand(instrumentation, COMMAND_REGISTER_SIM 222 + component.getPackageName() + "/" + component.getClassName() + " " 223 + handle.getId() + " " + PRIMARY_USER_SN + " " + label + " " + address); 224 } 225 226 public static void waitOnAllHandlers(Instrumentation instrumentation) throws Exception { 227 executeShellCommand(instrumentation, COMMAND_WAIT_ON_HANDLERS); 228 } 229 230 public static void waitOnLocalMainLooper(long timeoutMs) { 231 Handler mainHandler = new Handler(Looper.getMainLooper()); 232 final CountDownLatch lock = new CountDownLatch(1); 233 mainHandler.post(lock::countDown); 234 while (lock.getCount() > 0) { 235 try { 236 lock.await(timeoutMs, TimeUnit.MILLISECONDS); 237 } catch (InterruptedException e) { 238 // do nothing 239 } 240 } 241 } 242 243 /** 244 * Executes the given shell command and returns the output in a string. Note that even 245 * if we don't care about the output, we have to read the stream completely to make the 246 * command execute. 247 */ 248 public static String executeShellCommand(Instrumentation instrumentation, 249 String command) throws Exception { 250 final ParcelFileDescriptor pfd = 251 instrumentation.getUiAutomation().executeShellCommand(command); 252 BufferedReader br = null; 253 try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) { 254 br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 255 String str = null; 256 StringBuilder out = new StringBuilder(); 257 while ((str = br.readLine()) != null) { 258 out.append(str); 259 } 260 return out.toString(); 261 } finally { 262 if (br != null) { 263 closeQuietly(br); 264 } 265 closeQuietly(pfd); 266 } 267 } 268 269 private static void closeQuietly(AutoCloseable closeable) { 270 if (closeable != null) { 271 try { 272 closeable.close(); 273 } catch (RuntimeException rethrown) { 274 throw rethrown; 275 } catch (Exception ignored) { 276 } 277 } 278 } 279 280 /** 281 * Waits for the {@link CountDownLatch} to count down to 0 and then returns without reseting 282 * the latch. 283 * @param lock the latch that the system will wait on. 284 * @return true if the latch was released successfully, false if the latch timed out before 285 * resetting. 286 */ 287 public static boolean waitForLatchCountDown(CountDownLatch lock) { 288 if (lock == null) { 289 return false; 290 } 291 292 boolean success; 293 try { 294 success = lock.await(5000, TimeUnit.MILLISECONDS); 295 } catch (InterruptedException ie) { 296 return false; 297 } 298 299 return success; 300 } 301 302 /** 303 * Waits for the {@link CountDownLatch} to count down to 0 and then returns a new reset latch. 304 * @param lock The lock that will await a countDown to 0. 305 * @return a new reset {@link CountDownLatch} if the lock successfully counted down to 0 or 306 * null if the operation timed out. 307 */ 308 public static CountDownLatch waitForLock(CountDownLatch lock) { 309 boolean success = waitForLatchCountDown(lock); 310 if (success) { 311 return new CountDownLatch(1); 312 } else { 313 return null; 314 } 315 } 316 317 /** 318 * Adds a new incoming call. 319 * 320 * @param instrumentation the Instrumentation, used for shell command execution. 321 * @param telecomManager the TelecomManager. 322 * @param handle the PhoneAccountHandle associated with the call. 323 * @param address the incoming address. 324 * @return the new self-managed incoming call. 325 */ 326 public static void addIncomingCall(Instrumentation instrumentation, 327 TelecomManager telecomManager, PhoneAccountHandle handle, 328 Uri address) { 329 330 // Inform telecom of new incoming self-managed connection. 331 Bundle extras = new Bundle(); 332 extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, address); 333 telecomManager.addNewIncomingCall(handle, extras); 334 335 // Wait for Telecom to finish creating the new connection. 336 try { 337 waitOnAllHandlers(instrumentation); 338 } catch (Exception e) { 339 TestCase.fail("Failed to wait on handlers"); 340 } 341 } 342 public static boolean hasBluetoothFeature() { 343 return InstrumentationRegistry.getContext().getPackageManager(). 344 hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 345 } 346 public static BluetoothDevice makeBluetoothDevice(String address) { 347 if (!HAS_BLUETOOTH) return null; 348 Parcel p1 = Parcel.obtain(); 349 p1.writeString(address); 350 p1.setDataPosition(0); 351 BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1); 352 p1.recycle(); 353 return device; 354 } 355 356 /** 357 * Places a new outgoing call. 358 * 359 * @param telecomManager the TelecomManager. 360 * @param handle the PhoneAccountHandle associated with the call. 361 * @param address outgoing call address. 362 * @return the new self-managed outgoing call. 363 */ 364 public static void placeOutgoingCall(Instrumentation instrumentation, 365 TelecomManager telecomManager, PhoneAccountHandle handle, 366 Uri address) { 367 // Inform telecom of new incoming self-managed connection. 368 Bundle extras = new Bundle(); 369 extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle); 370 telecomManager.placeCall(address, extras); 371 372 // Wait for Telecom to finish creating the new connection. 373 try { 374 waitOnAllHandlers(instrumentation); 375 } catch (Exception e) { 376 TestCase.fail("Failed to wait on handlers"); 377 } 378 } 379 380 /** 381 * Waits for a new SelfManagedConnection with the given address to be added. 382 * @param address The expected address. 383 * @return The SelfManagedConnection found. 384 */ 385 public static SelfManagedConnection waitForAndGetConnection(Uri address) { 386 // Wait for creation of the new connection. 387 if (!CtsSelfManagedConnectionService.waitForBinding()) { 388 TestCase.fail("Could not bind to Self-Managed ConnectionService"); 389 } 390 CtsSelfManagedConnectionService connectionService = 391 CtsSelfManagedConnectionService.getConnectionService(); 392 TestCase.assertTrue(connectionService.waitForUpdate( 393 CtsSelfManagedConnectionService.CONNECTION_CREATED_LOCK)); 394 395 Optional<SelfManagedConnection> connectionOptional = connectionService.getConnections() 396 .stream() 397 .filter(connection -> address.equals(connection.getAddress())) 398 .findFirst(); 399 assert(connectionOptional.isPresent()); 400 return connectionOptional.get(); 401 } 402 403 /** 404 * Utility class used to track the number of times a callback was invoked, and the arguments it 405 * was invoked with. This class is prefixed Invoke rather than the more typical Call for 406 * disambiguation purposes. 407 */ 408 public static final class InvokeCounter { 409 private final String mName; 410 private final Object mLock = new Object(); 411 private final ArrayList<Object[]> mInvokeArgs = new ArrayList<>(); 412 413 private int mInvokeCount; 414 415 public InvokeCounter(String callbackName) { 416 mName = callbackName; 417 } 418 419 public void invoke(Object... args) { 420 synchronized (mLock) { 421 mInvokeCount++; 422 mInvokeArgs.add(args); 423 mLock.notifyAll(); 424 } 425 } 426 427 public Object[] getArgs(int index) { 428 synchronized (mLock) { 429 return mInvokeArgs.get(index); 430 } 431 } 432 433 public int getInvokeCount() { 434 synchronized (mLock) { 435 return mInvokeCount; 436 } 437 } 438 439 public void waitForCount(int count) { 440 waitForCount(count, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); 441 } 442 443 public void waitForCount(int count, long timeoutMillis) { 444 waitForCount(count, timeoutMillis, null); 445 } 446 447 public void waitForCount(long timeoutMillis) { 448 synchronized (mLock) { 449 try { 450 mLock.wait(timeoutMillis); 451 }catch (InterruptedException ex) { 452 ex.printStackTrace(); 453 } 454 } 455 } 456 457 public void waitForCount(int count, long timeoutMillis, String message) { 458 synchronized (mLock) { 459 final long startTimeMillis = SystemClock.uptimeMillis(); 460 while (mInvokeCount < count) { 461 try { 462 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 463 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 464 if (remainingTimeMillis <= 0) { 465 if (message != null) { 466 TestCase.fail(message); 467 } else { 468 TestCase.fail(String.format("Expected %s to be called %d times.", 469 mName, count)); 470 } 471 } 472 mLock.wait(timeoutMillis); 473 } catch (InterruptedException ie) { 474 /* ignore */ 475 } 476 } 477 } 478 } 479 480 /** 481 * Waits for a predicate to return {@code true} within the specified timeout. Uses the 482 * {@link #mLock} for this {@link InvokeCounter} to eliminate the need to perform busy-wait. 483 * @param predicate The predicate. 484 * @param timeoutMillis The timeout. 485 */ 486 public void waitForPredicate(Predicate predicate, long timeoutMillis) { 487 synchronized (mLock) { 488 mInvokeArgs.clear(); 489 long startTimeMillis = SystemClock.uptimeMillis(); 490 long elapsedTimeMillis = 0; 491 long remainingTimeMillis = timeoutMillis; 492 Object foundValue = null; 493 boolean wasFound = false; 494 do { 495 try { 496 mLock.wait(timeoutMillis); 497 foundValue = (mInvokeArgs.get(mInvokeArgs.size()-1))[0]; 498 wasFound = predicate.test(foundValue); 499 elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 500 remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 501 } catch (InterruptedException ie) { 502 /* ignore */ 503 } 504 } while (!wasFound && remainingTimeMillis > 0); 505 if (wasFound) { 506 return; 507 } else if (remainingTimeMillis <= 0) { 508 TestCase.fail("Expected value not found within time limit"); 509 } 510 } 511 } 512 } 513 } 514