| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.telecom.cts; |
| |
| import com.android.compatibility.common.util.ApiLevelUtil; |
| |
| import android.app.Instrumentation; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.graphics.Color; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.ParcelFileDescriptor; |
| import android.os.Process; |
| import android.os.SystemClock; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.BufferedReader; |
| import java.io.FileInputStream; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Optional; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Predicate; |
| |
| public class TestUtils { |
| static final String TAG = "TelecomCTSTests"; |
| static final boolean HAS_TELECOM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; |
| static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000; |
| static final long WAIT_FOR_CALL_ADDED_TIMEOUT_S = 15; |
| static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_CALLBACK = 50; |
| |
| // Non-final to allow modification by tests not in this package (e.g. permission-related |
| // tests in the Telecom2 test package. |
| public static String PACKAGE = "android.telecom.cts"; |
| public static final String COMPONENT = "android.telecom.cts.CtsConnectionService"; |
| public static final String SELF_MANAGED_COMPONENT = |
| "android.telecom.cts.CtsSelfManagedConnectionService"; |
| public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService"; |
| public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID"; |
| public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE = |
| new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID); |
| public static final String REMOTE_ACCOUNT_ID = "xtstest_REMOTE_CALL_PROVIDER_ID"; |
| public static final String SELF_MANAGED_ACCOUNT_ID_1 = "ctstest_SELF_MANAGED_ID_1"; |
| public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_1 = |
| new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), |
| SELF_MANAGED_ACCOUNT_ID_1); |
| public static final String SELF_MANAGED_ACCOUNT_ID_2 = "ctstest_SELF_MANAGED_ID_2"; |
| public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_2 = |
| new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT), |
| SELF_MANAGED_ACCOUNT_ID_2); |
| |
| public static final String ACCOUNT_LABEL = "CTSConnectionService"; |
| public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder( |
| TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL) |
| .setAddress(Uri.parse("tel:555-TEST")) |
| .setSubscriptionAddress(Uri.parse("tel:555-TEST")) |
| .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER | |
| PhoneAccount.CAPABILITY_VIDEO_CALLING | |
| PhoneAccount.CAPABILITY_CONNECTION_MANAGER) |
| .setHighlightColor(Color.RED) |
| .setShortDescription(ACCOUNT_LABEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) |
| .build(); |
| public static final String REMOTE_ACCOUNT_LABEL = "CTSRemoteConnectionService"; |
| public static final String SELF_MANAGED_ACCOUNT_LABEL = "android.telecom.cts"; |
| public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = PhoneAccount.builder( |
| TEST_SELF_MANAGED_HANDLE_2, SELF_MANAGED_ACCOUNT_LABEL) |
| .setAddress(Uri.parse("sip:test@test.com")) |
| .setSubscriptionAddress(Uri.parse("sip:test@test.com")) |
| .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | |
| PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING | |
| PhoneAccount.CAPABILITY_VIDEO_CALLING) |
| .setHighlightColor(Color.BLUE) |
| .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_SIP) |
| .build(); |
| public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_1 = PhoneAccount.builder( |
| TEST_SELF_MANAGED_HANDLE_1, SELF_MANAGED_ACCOUNT_LABEL) |
| .setAddress(Uri.parse("sip:test@test.com")) |
| .setSubscriptionAddress(Uri.parse("sip:test@test.com")) |
| .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | |
| PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING | |
| PhoneAccount.CAPABILITY_VIDEO_CALLING) |
| .setHighlightColor(Color.BLUE) |
| .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_SIP) |
| .build(); |
| |
| private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer "; |
| |
| private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer"; |
| |
| private static final String COMMAND_GET_SYSTEM_DIALER = "telecom get-system-dialer"; |
| |
| private static final String COMMAND_ENABLE = "telecom set-phone-account-enabled "; |
| |
| private static final String COMMAND_REGISTER_SIM = "telecom register-sim-phone-account "; |
| |
| private static final String COMMAND_WAIT_ON_HANDLERS = "telecom wait-on-handlers"; |
| |
| public static final String MERGE_CALLER_NAME = "calls-merged"; |
| public static final String SWAP_CALLER_NAME = "calls-swapped"; |
| private static final String PRIMARY_USER_SN = "0"; |
| |
| public static boolean shouldTestTelecom(Context context) { |
| if (!HAS_TELECOM) { |
| return false; |
| } |
| final PackageManager pm = context.getPackageManager(); |
| return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && |
| pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE); |
| } |
| |
| public static String setDefaultDialer(Instrumentation instrumentation, String packageName) |
| throws Exception { |
| return executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER + packageName); |
| } |
| |
| public static String getDefaultDialer(Instrumentation instrumentation) throws Exception { |
| return executeShellCommand(instrumentation, COMMAND_GET_DEFAULT_DIALER); |
| } |
| |
| public static String getSystemDialer(Instrumentation instrumentation) throws Exception { |
| return executeShellCommand(instrumentation, COMMAND_GET_SYSTEM_DIALER); |
| } |
| |
| public static void enablePhoneAccount(Instrumentation instrumentation, |
| PhoneAccountHandle handle) throws Exception { |
| final ComponentName component = handle.getComponentName(); |
| executeShellCommand(instrumentation, COMMAND_ENABLE |
| + component.getPackageName() + "/" + component.getClassName() + " " |
| + handle.getId() + " " + PRIMARY_USER_SN); |
| } |
| |
| public static void registerSimPhoneAccount(Instrumentation instrumentation, |
| PhoneAccountHandle handle, String label, String address) throws Exception { |
| final ComponentName component = handle.getComponentName(); |
| executeShellCommand(instrumentation, COMMAND_REGISTER_SIM |
| + component.getPackageName() + "/" + component.getClassName() + " " |
| + handle.getId() + " " + PRIMARY_USER_SN + " " + label + " " + address); |
| } |
| |
| public static void waitOnAllHandlers(Instrumentation instrumentation) throws Exception { |
| executeShellCommand(instrumentation, COMMAND_WAIT_ON_HANDLERS); |
| } |
| |
| /** |
| * Executes the given shell command and returns the output in a string. Note that even |
| * if we don't care about the output, we have to read the stream completely to make the |
| * command execute. |
| */ |
| public static String executeShellCommand(Instrumentation instrumentation, |
| String command) throws Exception { |
| final ParcelFileDescriptor pfd = |
| instrumentation.getUiAutomation().executeShellCommand(command); |
| BufferedReader br = null; |
| try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) { |
| br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); |
| String str = null; |
| StringBuilder out = new StringBuilder(); |
| while ((str = br.readLine()) != null) { |
| out.append(str); |
| } |
| return out.toString(); |
| } finally { |
| if (br != null) { |
| closeQuietly(br); |
| } |
| closeQuietly(pfd); |
| } |
| } |
| |
| private static void closeQuietly(AutoCloseable closeable) { |
| if (closeable != null) { |
| try { |
| closeable.close(); |
| } catch (RuntimeException rethrown) { |
| throw rethrown; |
| } catch (Exception ignored) { |
| } |
| } |
| } |
| |
| /** |
| * Waits for the {@link CountDownLatch} to count down to 0 and then returns without reseting |
| * the latch. |
| * @param lock the latch that the system will wait on. |
| * @return true if the latch was released successfully, false if the latch timed out before |
| * resetting. |
| */ |
| public static boolean waitForLatchCountDown(CountDownLatch lock) { |
| if (lock == null) { |
| return false; |
| } |
| |
| boolean success; |
| try { |
| success = lock.await(5000, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException ie) { |
| return false; |
| } |
| |
| return success; |
| } |
| |
| /** |
| * Waits for the {@link CountDownLatch} to count down to 0 and then returns a new reset latch. |
| * @param lock The lock that will await a countDown to 0. |
| * @return a new reset {@link CountDownLatch} if the lock successfully counted down to 0 or |
| * null if the operation timed out. |
| */ |
| public static CountDownLatch waitForLock(CountDownLatch lock) { |
| boolean success = waitForLatchCountDown(lock); |
| if (success) { |
| return new CountDownLatch(1); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Adds a new incoming call. |
| * |
| * @param instrumentation the Instrumentation, used for shell command execution. |
| * @param telecomManager the TelecomManager. |
| * @param handle the PhoneAccountHandle associated with the call. |
| * @param address the incoming address. |
| * @return the new self-managed incoming call. |
| */ |
| public static void addIncomingCall(Instrumentation instrumentation, |
| TelecomManager telecomManager, PhoneAccountHandle handle, |
| Uri address) { |
| |
| // Inform telecom of new incoming self-managed connection. |
| Bundle extras = new Bundle(); |
| extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, address); |
| telecomManager.addNewIncomingCall(handle, extras); |
| |
| // Wait for Telecom to finish creating the new connection. |
| try { |
| waitOnAllHandlers(instrumentation); |
| } catch (Exception e) { |
| TestCase.fail("Failed to wait on handlers"); |
| } |
| } |
| |
| /** |
| * Places a new outgoing call. |
| * |
| * @param telecomManager the TelecomManager. |
| * @param handle the PhoneAccountHandle associated with the call. |
| * @param address outgoing call address. |
| * @return the new self-managed outgoing call. |
| */ |
| public static void placeOutgoingCall(Instrumentation instrumentation, |
| TelecomManager telecomManager, PhoneAccountHandle handle, |
| Uri address) { |
| // Inform telecom of new incoming self-managed connection. |
| Bundle extras = new Bundle(); |
| extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle); |
| telecomManager.placeCall(address, extras); |
| |
| // Wait for Telecom to finish creating the new connection. |
| try { |
| waitOnAllHandlers(instrumentation); |
| } catch (Exception e) { |
| TestCase.fail("Failed to wait on handlers"); |
| } |
| } |
| |
| /** |
| * Waits for a new SelfManagedConnection with the given address to be added. |
| * @param address The expected address. |
| * @return The SelfManagedConnection found. |
| */ |
| public static SelfManagedConnection waitForAndGetConnection(Uri address) { |
| // Wait for creation of the new connection. |
| if (!CtsSelfManagedConnectionService.waitForBinding()) { |
| TestCase.fail("Could not bind to Self-Managed ConnectionService"); |
| } |
| CtsSelfManagedConnectionService connectionService = |
| CtsSelfManagedConnectionService.getConnectionService(); |
| TestCase.assertTrue(connectionService.waitForUpdate( |
| CtsSelfManagedConnectionService.CONNECTION_CREATED_LOCK)); |
| |
| Optional<SelfManagedConnection> connectionOptional = connectionService.getConnections() |
| .stream() |
| .filter(connection -> address.equals(connection.getAddress())) |
| .findFirst(); |
| assert(connectionOptional.isPresent()); |
| return connectionOptional.get(); |
| } |
| |
| /** |
| * Utility class used to track the number of times a callback was invoked, and the arguments it |
| * was invoked with. This class is prefixed Invoke rather than the more typical Call for |
| * disambiguation purposes. |
| */ |
| public static final class InvokeCounter { |
| private final String mName; |
| private final Object mLock = new Object(); |
| private final ArrayList<Object[]> mInvokeArgs = new ArrayList<>(); |
| |
| private int mInvokeCount; |
| |
| public InvokeCounter(String callbackName) { |
| mName = callbackName; |
| } |
| |
| public void invoke(Object... args) { |
| synchronized (mLock) { |
| mInvokeCount++; |
| mInvokeArgs.add(args); |
| mLock.notifyAll(); |
| } |
| } |
| |
| public Object[] getArgs(int index) { |
| synchronized (mLock) { |
| return mInvokeArgs.get(index); |
| } |
| } |
| |
| public int getInvokeCount() { |
| synchronized (mLock) { |
| return mInvokeCount; |
| } |
| } |
| |
| public void waitForCount(int count) { |
| waitForCount(count, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); |
| } |
| |
| public void waitForCount(int count, long timeoutMillis) { |
| waitForCount(count, timeoutMillis, null); |
| } |
| |
| public void waitForCount(long timeoutMillis) { |
| synchronized (mLock) { |
| try { |
| mLock.wait(timeoutMillis); |
| }catch (InterruptedException ex) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| |
| public void waitForCount(int count, long timeoutMillis, String message) { |
| synchronized (mLock) { |
| final long startTimeMillis = SystemClock.uptimeMillis(); |
| while (mInvokeCount < count) { |
| try { |
| final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; |
| final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; |
| if (remainingTimeMillis <= 0) { |
| if (message != null) { |
| TestCase.fail(message); |
| } else { |
| TestCase.fail(String.format("Expected %s to be called %d times.", |
| mName, count)); |
| } |
| } |
| mLock.wait(timeoutMillis); |
| } catch (InterruptedException ie) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| |
| /** |
| * Waits for a predicate to return {@code true} within the specified timeout. Uses the |
| * {@link #mLock} for this {@link InvokeCounter} to eliminate the need to perform busy-wait. |
| * @param predicate The predicate. |
| * @param timeoutMillis The timeout. |
| */ |
| public void waitForPredicate(Predicate predicate, long timeoutMillis) { |
| synchronized (mLock) { |
| mInvokeArgs.clear(); |
| long startTimeMillis = SystemClock.uptimeMillis(); |
| long elapsedTimeMillis = 0; |
| long remainingTimeMillis = timeoutMillis; |
| Object foundValue = null; |
| boolean wasFound = false; |
| do { |
| try { |
| mLock.wait(timeoutMillis); |
| foundValue = (mInvokeArgs.get(mInvokeArgs.size()-1))[0]; |
| wasFound = predicate.test(foundValue); |
| elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; |
| remainingTimeMillis = timeoutMillis - elapsedTimeMillis; |
| } catch (InterruptedException ie) { |
| /* ignore */ |
| } |
| } while (!wasFound && remainingTimeMillis > 0); |
| if (wasFound) { |
| return; |
| } else if (remainingTimeMillis <= 0) { |
| TestCase.fail("Expected value not found within time limit"); |
| } |
| } |
| } |
| } |
| } |