/* * 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 com.android.internal.annotations.GuardedBy; 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. *

* Typical usage is: *

*


 * public class MyMethodCaller extends TimeoutRemoteCallHelper {
 *     // 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);
 *     }
 * }
 * 

* * @param The type of the expected result. * * @hide */ public abstract class TimedRemoteCaller { public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000; private final Object mLock = new Object(); private final long mCallTimeoutMillis; /** The callbacks we are waiting for, key == sequence id, value == 1 */ @GuardedBy("mLock") private final SparseIntArray mAwaitedCalls = new SparseIntArray(1); /** The callbacks we received but for which the result has not yet been reported */ @GuardedBy("mLock") private final SparseArray mReceivedCalls = new SparseArray<>(1); @GuardedBy("mLock") private int mSequenceCounter; /** * Create a new timed caller. * * @param callTimeoutMillis The time to wait in {@link #getResultTimed} before a timed call will * be declared timed out */ public TimedRemoteCaller(long callTimeoutMillis) { mCallTimeoutMillis = callTimeoutMillis; } /** * Indicate that a timed call will be made. * * @return The sequence id for the call */ protected final int onBeforeRemoteCall() { synchronized (mLock) { int sequenceId; do { sequenceId = mSequenceCounter++; } while (mAwaitedCalls.get(sequenceId) != 0); mAwaitedCalls.put(sequenceId, 1); return sequenceId; } } /** * Indicate that the timed call has returned. * * @param result The result of the timed call * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()}) */ protected final void onRemoteMethodResult(T result, int sequence) { synchronized (mLock) { // Do nothing if we do not await the call anymore as it must have timed out boolean containedSequenceId = mAwaitedCalls.get(sequence) != 0; if (containedSequenceId) { mAwaitedCalls.delete(sequence); mReceivedCalls.put(sequence, result); mLock.notifyAll(); } } } /** * Wait until the timed call has returned. * * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()}) * * @return The result of the timed call (set in {@link #onRemoteMethodResult(Object, int)}) */ protected final T getResultTimed(int sequence) throws TimeoutException { final long startMillis = SystemClock.uptimeMillis(); while (true) { try { synchronized (mLock) { if (mReceivedCalls.indexOfKey(sequence) >= 0) { return mReceivedCalls.removeReturnOld(sequence); } final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; final long waitMillis = mCallTimeoutMillis - elapsedMillis; if (waitMillis <= 0) { mAwaitedCalls.delete(sequence); throw new TimeoutException("No response for sequence: " + sequence); } mLock.wait(waitMillis); } } catch (InterruptedException ie) { /* ignore */ } } } }