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