| /* |
| * 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. |
| * <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 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<T> 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 */ |
| } |
| } |
| } |
| } |