Home | History | Annotate | Download | only in cts
      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