Loading src/java/com/android/internal/telephony/Connection.java +18 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,8 @@ public abstract class Connection { public void onCallPullFailed(Connection externalConnection); public void onHandoverToWifiFailed(); public void onConnectionEvent(String event, Bundle extras); public void onRttModifyRequestReceived(); public void onRttModifyResponseReceived(int status); } /** Loading Loading @@ -136,6 +138,10 @@ public abstract class Connection { public void onHandoverToWifiFailed() {} @Override public void onConnectionEvent(String event, Bundle extras) {} @Override public void onRttModifyRequestReceived() {} @Override public void onRttModifyResponseReceived(int status) {} } public static final int AUDIO_QUALITY_STANDARD = 1; Loading Loading @@ -1006,6 +1012,18 @@ public abstract class Connection { public void pullExternalCall() { } public void onRttModifyRequestReceived() { for (Listener l : mListeners) { l.onRttModifyRequestReceived(); } } public void onRttModifyResponseReceived(int status) { for (Listener l : mListeners) { l.onRttModifyResponseReceived(status); } } /** * */ Loading src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +28 −0 Original line number Diff line number Diff line Loading @@ -2325,6 +2325,34 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } } @Override public void onRttModifyRequestReceived(ImsCall imsCall) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttModifyRequestReceived(); } } @Override public void onRttModifyResponseReceived(ImsCall imsCall, int status) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttModifyResponseReceived(status); if (status == android.telecom.Connection.RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) { conn.startRttTextProcessing(); } } } @Override public void onRttMessageReceived(ImsCall imsCall, String message) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttMessageReceived(message); } } /** * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated * {@link ImsPhoneConnection} of the change. Loading src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java +48 −0 Original line number Diff line number Diff line Loading @@ -104,6 +104,9 @@ public class ImsPhoneConnection extends Connection implements private int mPreciseDisconnectCause = 0; private ImsRttTextHandler mRttTextHandler; private android.telecom.Connection.RttTextStream mRttTextStream; //***** Event Constants private static final int EVENT_DTMF_DONE = 1; private static final int EVENT_PAUSE_DONE = 2; Loading Loading @@ -889,6 +892,51 @@ public class ImsPhoneConnection extends Connection implements return changed; } public void sendRttModifyRequest(android.telecom.Connection.RttTextStream textStream) { getImsCall().sendRttModifyRequest(); setCurrentRttTextStream(textStream); } /** * Sends the user's response to a remotely-issued RTT upgrade request * * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user * accepts, {@code null} if not. */ public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) { boolean accept = textStream != null; ImsCall imsCall = getImsCall(); imsCall.sendRttModifyResponse(accept); if (accept) { setCurrentRttTextStream(textStream); startRttTextProcessing(); } else { Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections"); } } public void onRttMessageReceived(String message) { getOrCreateRttTextHandler().sendToInCall(message); } public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) { mRttTextStream = rttTextStream; } public void startRttTextProcessing() { getOrCreateRttTextHandler().initialize(mRttTextStream); } private ImsRttTextHandler getOrCreateRttTextHandler() { if (mRttTextHandler != null) { return mRttTextHandler; } mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(), (message) -> getImsCall().sendRttMessage(message)); return mRttTextHandler; } /** * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. * The call is considered to be a WIFI call if the extra value is Loading src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java 0 → 100644 +203 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 com.android.internal.telephony.imsphone; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.telecom.Connection; import android.telephony.Rlog; import java.io.IOException; public class ImsRttTextHandler extends Handler { public interface NetworkWriter { void write(String s); } private static final String LOG_TAG = "ImsRttTextHandler"; // RTT buffering and sending tuning constants. // TODO: put this in carrier config? // These count Unicode codepoints, not Java char types. public static final int MAX_CODEPOINTS_PER_SECOND = 30; // Assuming that we do not exceed the rate limit, this is the maximum time between when a // piece of text is received and when it is actually sent over the network. public static final int MAX_BUFFERING_DELAY_MILLIS = 200; // Assuming that we do not exceed the rate limit, this is the maximum size we will allow // the buffer to grow to before sending as many as we can. public static final int MAX_BUFFERED_CHARACTER_COUNT = 5; private static final int MILLIS_PER_SECOND = 1000; // Messages for the handler. // Initializes the text handler. Should have an RttTextStream set in msg.obj private static final int INITIALIZE = 1; // Appends a string to the buffer to send to the network. Should have the string in msg.obj private static final int APPEND_TO_NETWORK_BUFFER = 2; // Send a string received from the network to the in-call app. Should have the string in // msg.obj. private static final int SEND_TO_INCALL = 3; // Send as many characters as possible, as constrained by the rate limit. No extra data. private static final int ATTEMPT_SEND_TO_NETWORK = 4; // Indicates that N characters were sent a second ago and should be ignored by the rate // limiter. msg.arg1 should be set to N. private static final int EXPIRE_SENT_CODEPOINT_COUNT = 5; // Indicates that the call is over and we should teardown everything we have set up. private static final int TEARDOWN = 6; private Connection.RttTextStream mRttTextStream; private class InCallReaderThread extends Thread { private final Connection.RttTextStream mReaderThreadRttTextStream; public InCallReaderThread(Connection.RttTextStream textStream) { mReaderThreadRttTextStream = textStream; } @Override public void run() { while (true) { String charsReceived; try { charsReceived = mReaderThreadRttTextStream.read(); } catch (IOException e) { Rlog.e(LOG_TAG, "RttReaderThread - IOException encountered " + "reading from in-call: %s", e); obtainMessage(TEARDOWN).sendToTarget(); break; } if (charsReceived == null) { if (Thread.currentThread().isInterrupted()) { Rlog.i(LOG_TAG, "RttReaderThread - Thread interrupted. Finishing."); break; } Rlog.e(LOG_TAG, "RttReaderThread - Stream closed unexpectedly. Attempt to " + "reinitialize."); obtainMessage(TEARDOWN).sendToTarget(); break; } if (charsReceived.length() == 0) { continue; } obtainMessage(APPEND_TO_NETWORK_BUFFER, charsReceived) .sendToTarget(); } } } private int mCodepointsAvailableForTransmission = MAX_CODEPOINTS_PER_SECOND; private StringBuffer mBufferedTextToNetwork = new StringBuffer(); private InCallReaderThread mReaderThread; // This is only ever used when the pipes fail and we have to re-setup. Messages received // from the network are buffered here until Telecom gets back to us with the new pipes. private StringBuffer mBufferedTextToIncall = new StringBuffer(); private final NetworkWriter mNetworkWriter; @Override public void handleMessage(Message msg) { switch (msg.what) { case INITIALIZE: if (mRttTextStream != null || mReaderThread != null) { Rlog.e(LOG_TAG, "RTT text stream already initialized. Ignoring."); return; } mRttTextStream = (Connection.RttTextStream) msg.obj; mReaderThread = new InCallReaderThread(mRttTextStream); mReaderThread.start(); break; case SEND_TO_INCALL: String messageToIncall = (String) msg.obj; try { mRttTextStream.write(messageToIncall); } catch (IOException e) { Rlog.e(LOG_TAG, "IOException encountered writing to in-call: %s", e); obtainMessage(TEARDOWN).sendToTarget(); mBufferedTextToIncall.append(messageToIncall); } break; case APPEND_TO_NETWORK_BUFFER: // First, append the text-to-send to the string buffer mBufferedTextToNetwork.append((String) msg.obj); // Check to see how many codepoints we have buffered. If we have more than 5, // send immediately, otherwise, wait until a timeout happens. int numCodepointsBuffered = mBufferedTextToNetwork .codePointCount(0, mBufferedTextToNetwork.length()); if (numCodepointsBuffered >= MAX_BUFFERED_CHARACTER_COUNT) { sendMessageAtFrontOfQueue(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); } else { sendEmptyMessageDelayed( ATTEMPT_SEND_TO_NETWORK, MAX_BUFFERING_DELAY_MILLIS); } break; case ATTEMPT_SEND_TO_NETWORK: // Check to see how many codepoints we can send, and send that many. int numCodePointsAvailableInBuffer = mBufferedTextToNetwork.codePointCount(0, mBufferedTextToNetwork.length()); int numCodePointsSent = Math.min(numCodePointsAvailableInBuffer, mCodepointsAvailableForTransmission); if (numCodePointsSent == 0) { break; } int endSendIndex = mBufferedTextToNetwork.offsetByCodePoints(0, numCodePointsSent); String stringToSend = mBufferedTextToNetwork.substring(0, endSendIndex); mBufferedTextToNetwork.delete(0, endSendIndex); mNetworkWriter.write(stringToSend); mCodepointsAvailableForTransmission -= numCodePointsSent; sendMessageDelayed( obtainMessage(EXPIRE_SENT_CODEPOINT_COUNT, numCodePointsSent, 0), MILLIS_PER_SECOND); break; case EXPIRE_SENT_CODEPOINT_COUNT: mCodepointsAvailableForTransmission += msg.arg1; if (mCodepointsAvailableForTransmission > 0) { sendMessageAtFrontOfQueue(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); } break; case TEARDOWN: try { if (mReaderThread != null) { mReaderThread.join(1000); } } catch (InterruptedException e) { // Ignore and assume it'll finish on its own. } mReaderThread = null; mRttTextStream = null; break; } } public ImsRttTextHandler(Looper looper, NetworkWriter networkWriter) { super(looper); mNetworkWriter = networkWriter; } public void sendToInCall(String msg) { obtainMessage(SEND_TO_INCALL, msg).sendToTarget(); } public void initialize(Connection.RttTextStream rttTextStream) { obtainMessage(INITIALIZE, rttTextStream).sendToTarget(); } public void tearDown() { obtainMessage(TEARDOWN).sendToTarget(); } } tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +26 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,8 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public abstract class TelephonyTest { protected static String TAG; Loading Loading @@ -476,4 +478,28 @@ public abstract class TelephonyTest { doReturn(mPackageInfo).when(mPackageManager).getPackageInfoAsUser( eq(TAG), anyInt(), anyInt()); } protected final void waitForHandlerAction(Handler h, long timeoutMillis) { final CountDownLatch lock = new CountDownLatch(1); h.post(lock::countDown); while (lock.getCount() > 0) { try { lock.await(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // do nothing } } } protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) { final CountDownLatch lock = new CountDownLatch(1); h.postDelayed(lock::countDown, delayMs); while (lock.getCount() > 0) { try { lock.await(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // do nothing } } } } Loading
src/java/com/android/internal/telephony/Connection.java +18 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,8 @@ public abstract class Connection { public void onCallPullFailed(Connection externalConnection); public void onHandoverToWifiFailed(); public void onConnectionEvent(String event, Bundle extras); public void onRttModifyRequestReceived(); public void onRttModifyResponseReceived(int status); } /** Loading Loading @@ -136,6 +138,10 @@ public abstract class Connection { public void onHandoverToWifiFailed() {} @Override public void onConnectionEvent(String event, Bundle extras) {} @Override public void onRttModifyRequestReceived() {} @Override public void onRttModifyResponseReceived(int status) {} } public static final int AUDIO_QUALITY_STANDARD = 1; Loading Loading @@ -1006,6 +1012,18 @@ public abstract class Connection { public void pullExternalCall() { } public void onRttModifyRequestReceived() { for (Listener l : mListeners) { l.onRttModifyRequestReceived(); } } public void onRttModifyResponseReceived(int status) { for (Listener l : mListeners) { l.onRttModifyResponseReceived(status); } } /** * */ Loading
src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +28 −0 Original line number Diff line number Diff line Loading @@ -2325,6 +2325,34 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } } @Override public void onRttModifyRequestReceived(ImsCall imsCall) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttModifyRequestReceived(); } } @Override public void onRttModifyResponseReceived(ImsCall imsCall, int status) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttModifyResponseReceived(status); if (status == android.telecom.Connection.RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) { conn.startRttTextProcessing(); } } } @Override public void onRttMessageReceived(ImsCall imsCall, String message) { ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.onRttMessageReceived(message); } } /** * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated * {@link ImsPhoneConnection} of the change. Loading
src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java +48 −0 Original line number Diff line number Diff line Loading @@ -104,6 +104,9 @@ public class ImsPhoneConnection extends Connection implements private int mPreciseDisconnectCause = 0; private ImsRttTextHandler mRttTextHandler; private android.telecom.Connection.RttTextStream mRttTextStream; //***** Event Constants private static final int EVENT_DTMF_DONE = 1; private static final int EVENT_PAUSE_DONE = 2; Loading Loading @@ -889,6 +892,51 @@ public class ImsPhoneConnection extends Connection implements return changed; } public void sendRttModifyRequest(android.telecom.Connection.RttTextStream textStream) { getImsCall().sendRttModifyRequest(); setCurrentRttTextStream(textStream); } /** * Sends the user's response to a remotely-issued RTT upgrade request * * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user * accepts, {@code null} if not. */ public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) { boolean accept = textStream != null; ImsCall imsCall = getImsCall(); imsCall.sendRttModifyResponse(accept); if (accept) { setCurrentRttTextStream(textStream); startRttTextProcessing(); } else { Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections"); } } public void onRttMessageReceived(String message) { getOrCreateRttTextHandler().sendToInCall(message); } public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) { mRttTextStream = rttTextStream; } public void startRttTextProcessing() { getOrCreateRttTextHandler().initialize(mRttTextStream); } private ImsRttTextHandler getOrCreateRttTextHandler() { if (mRttTextHandler != null) { return mRttTextHandler; } mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(), (message) -> getImsCall().sendRttMessage(message)); return mRttTextHandler; } /** * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. * The call is considered to be a WIFI call if the extra value is Loading
src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java 0 → 100644 +203 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 com.android.internal.telephony.imsphone; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.telecom.Connection; import android.telephony.Rlog; import java.io.IOException; public class ImsRttTextHandler extends Handler { public interface NetworkWriter { void write(String s); } private static final String LOG_TAG = "ImsRttTextHandler"; // RTT buffering and sending tuning constants. // TODO: put this in carrier config? // These count Unicode codepoints, not Java char types. public static final int MAX_CODEPOINTS_PER_SECOND = 30; // Assuming that we do not exceed the rate limit, this is the maximum time between when a // piece of text is received and when it is actually sent over the network. public static final int MAX_BUFFERING_DELAY_MILLIS = 200; // Assuming that we do not exceed the rate limit, this is the maximum size we will allow // the buffer to grow to before sending as many as we can. public static final int MAX_BUFFERED_CHARACTER_COUNT = 5; private static final int MILLIS_PER_SECOND = 1000; // Messages for the handler. // Initializes the text handler. Should have an RttTextStream set in msg.obj private static final int INITIALIZE = 1; // Appends a string to the buffer to send to the network. Should have the string in msg.obj private static final int APPEND_TO_NETWORK_BUFFER = 2; // Send a string received from the network to the in-call app. Should have the string in // msg.obj. private static final int SEND_TO_INCALL = 3; // Send as many characters as possible, as constrained by the rate limit. No extra data. private static final int ATTEMPT_SEND_TO_NETWORK = 4; // Indicates that N characters were sent a second ago and should be ignored by the rate // limiter. msg.arg1 should be set to N. private static final int EXPIRE_SENT_CODEPOINT_COUNT = 5; // Indicates that the call is over and we should teardown everything we have set up. private static final int TEARDOWN = 6; private Connection.RttTextStream mRttTextStream; private class InCallReaderThread extends Thread { private final Connection.RttTextStream mReaderThreadRttTextStream; public InCallReaderThread(Connection.RttTextStream textStream) { mReaderThreadRttTextStream = textStream; } @Override public void run() { while (true) { String charsReceived; try { charsReceived = mReaderThreadRttTextStream.read(); } catch (IOException e) { Rlog.e(LOG_TAG, "RttReaderThread - IOException encountered " + "reading from in-call: %s", e); obtainMessage(TEARDOWN).sendToTarget(); break; } if (charsReceived == null) { if (Thread.currentThread().isInterrupted()) { Rlog.i(LOG_TAG, "RttReaderThread - Thread interrupted. Finishing."); break; } Rlog.e(LOG_TAG, "RttReaderThread - Stream closed unexpectedly. Attempt to " + "reinitialize."); obtainMessage(TEARDOWN).sendToTarget(); break; } if (charsReceived.length() == 0) { continue; } obtainMessage(APPEND_TO_NETWORK_BUFFER, charsReceived) .sendToTarget(); } } } private int mCodepointsAvailableForTransmission = MAX_CODEPOINTS_PER_SECOND; private StringBuffer mBufferedTextToNetwork = new StringBuffer(); private InCallReaderThread mReaderThread; // This is only ever used when the pipes fail and we have to re-setup. Messages received // from the network are buffered here until Telecom gets back to us with the new pipes. private StringBuffer mBufferedTextToIncall = new StringBuffer(); private final NetworkWriter mNetworkWriter; @Override public void handleMessage(Message msg) { switch (msg.what) { case INITIALIZE: if (mRttTextStream != null || mReaderThread != null) { Rlog.e(LOG_TAG, "RTT text stream already initialized. Ignoring."); return; } mRttTextStream = (Connection.RttTextStream) msg.obj; mReaderThread = new InCallReaderThread(mRttTextStream); mReaderThread.start(); break; case SEND_TO_INCALL: String messageToIncall = (String) msg.obj; try { mRttTextStream.write(messageToIncall); } catch (IOException e) { Rlog.e(LOG_TAG, "IOException encountered writing to in-call: %s", e); obtainMessage(TEARDOWN).sendToTarget(); mBufferedTextToIncall.append(messageToIncall); } break; case APPEND_TO_NETWORK_BUFFER: // First, append the text-to-send to the string buffer mBufferedTextToNetwork.append((String) msg.obj); // Check to see how many codepoints we have buffered. If we have more than 5, // send immediately, otherwise, wait until a timeout happens. int numCodepointsBuffered = mBufferedTextToNetwork .codePointCount(0, mBufferedTextToNetwork.length()); if (numCodepointsBuffered >= MAX_BUFFERED_CHARACTER_COUNT) { sendMessageAtFrontOfQueue(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); } else { sendEmptyMessageDelayed( ATTEMPT_SEND_TO_NETWORK, MAX_BUFFERING_DELAY_MILLIS); } break; case ATTEMPT_SEND_TO_NETWORK: // Check to see how many codepoints we can send, and send that many. int numCodePointsAvailableInBuffer = mBufferedTextToNetwork.codePointCount(0, mBufferedTextToNetwork.length()); int numCodePointsSent = Math.min(numCodePointsAvailableInBuffer, mCodepointsAvailableForTransmission); if (numCodePointsSent == 0) { break; } int endSendIndex = mBufferedTextToNetwork.offsetByCodePoints(0, numCodePointsSent); String stringToSend = mBufferedTextToNetwork.substring(0, endSendIndex); mBufferedTextToNetwork.delete(0, endSendIndex); mNetworkWriter.write(stringToSend); mCodepointsAvailableForTransmission -= numCodePointsSent; sendMessageDelayed( obtainMessage(EXPIRE_SENT_CODEPOINT_COUNT, numCodePointsSent, 0), MILLIS_PER_SECOND); break; case EXPIRE_SENT_CODEPOINT_COUNT: mCodepointsAvailableForTransmission += msg.arg1; if (mCodepointsAvailableForTransmission > 0) { sendMessageAtFrontOfQueue(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); } break; case TEARDOWN: try { if (mReaderThread != null) { mReaderThread.join(1000); } } catch (InterruptedException e) { // Ignore and assume it'll finish on its own. } mReaderThread = null; mRttTextStream = null; break; } } public ImsRttTextHandler(Looper looper, NetworkWriter networkWriter) { super(looper); mNetworkWriter = networkWriter; } public void sendToInCall(String msg) { obtainMessage(SEND_TO_INCALL, msg).sendToTarget(); } public void initialize(Connection.RttTextStream rttTextStream) { obtainMessage(INITIALIZE, rttTextStream).sendToTarget(); } public void tearDown() { obtainMessage(TEARDOWN).sendToTarget(); } }
tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +26 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,8 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public abstract class TelephonyTest { protected static String TAG; Loading Loading @@ -476,4 +478,28 @@ public abstract class TelephonyTest { doReturn(mPackageInfo).when(mPackageManager).getPackageInfoAsUser( eq(TAG), anyInt(), anyInt()); } protected final void waitForHandlerAction(Handler h, long timeoutMillis) { final CountDownLatch lock = new CountDownLatch(1); h.post(lock::countDown); while (lock.getCount() > 0) { try { lock.await(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // do nothing } } } protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) { final CountDownLatch lock = new CountDownLatch(1); h.postDelayed(lock::countDown, delayMs); while (lock.getCount() > 0) { try { lock.await(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // do nothing } } } }