/*
* Copyright (C) 2013 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.util;
import android.os.SystemClock;
import java.util.concurrent.TimeoutException;
/**
* This is a helper class for making an async one way call and
* its async one way response response in a sync fashion within
* a timeout. The key idea is to call the remote method with a
* sequence number and a callback and then starting to wait for
* the response. The remote method calls back with the result and
* the sequence number. If the response comes within the timeout
* and its sequence number is the one sent in the method invocation,
* then the call succeeded. If the response does not come within
* the timeout then the call failed. Older result received when
* waiting for the result are ignored.
* <p>
* Typical usage is:
* </p>
* <p><pre><code>
* public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> {
* // The one way remote method to call.
* private final IRemoteInterface mTarget;
*
* // One way callback invoked when the remote method is done.
* private final IRemoteCallback mCallback = new IRemoteCallback.Stub() {
* public void onCompleted(Object result, int sequence) {
* onRemoteMethodResult(result, sequence);
* }
* };
*
* public MyMethodCaller(IRemoteInterface target) {
* mTarget = target;
* }
*
* public Object onCallMyMethod(Object arg) throws RemoteException {
* final int sequence = onBeforeRemoteCall();
* mTarget.myMethod(arg, sequence);
* return getResultTimed(sequence);
* }
* }
* </code></pre></p>
*
* @param <T> The type of the expected result.
*
* @hide
*/
public abstract class TimedRemoteCaller<T> {
public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000;
private static final int UNDEFINED_SEQUENCE = -1;
private final Object mLock = new Object();
private final long mCallTimeoutMillis;
private int mSequenceCounter;
private int mReceivedSequence = UNDEFINED_SEQUENCE;
private int mAwaitedSequence = UNDEFINED_SEQUENCE;
private T mResult;
public TimedRemoteCaller(long callTimeoutMillis) {
mCallTimeoutMillis = callTimeoutMillis;
}
public final int onBeforeRemoteCall() {
synchronized (mLock) {
mAwaitedSequence = mSequenceCounter++;
return mAwaitedSequence;
}
}
public final T getResultTimed(int sequence) throws TimeoutException {
synchronized (mLock) {
final boolean success = waitForResultTimedLocked(sequence);
if (!success) {
throw new TimeoutException("No reponse for sequence: " + sequence);
}
T result = mResult;
mResult = null;
return result;
}
}
public final void onRemoteMethodResult(T result, int sequence) {
synchronized (mLock) {
if (sequence == mAwaitedSequence) {
mReceivedSequence = sequence;
mResult = result;
mLock.notifyAll();
}
}
}
private boolean waitForResultTimedLocked(int sequence) {
final long startMillis = SystemClock.uptimeMillis();
while (true) {
try {
if (mReceivedSequence == sequence) {
return true;
}
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long waitMillis = mCallTimeoutMillis - elapsedMillis;
if (waitMillis <= 0) {
return false;
}
mLock.wait(waitMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}
|