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 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