1 /* 2 * Copyright (C) 2013 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 17 package android.util; 18 19 import android.os.SystemClock; 20 import com.android.internal.annotations.GuardedBy; 21 22 import java.util.concurrent.TimeoutException; 23 24 /** 25 * This is a helper class for making an async one way call and 26 * its async one way response response in a sync fashion within 27 * a timeout. The key idea is to call the remote method with a 28 * sequence number and a callback and then starting to wait for 29 * the response. The remote method calls back with the result and 30 * the sequence number. If the response comes within the timeout 31 * and its sequence number is the one sent in the method invocation, 32 * then the call succeeded. If the response does not come within 33 * the timeout then the call failed. 34 * <p> 35 * Typical usage is: 36 * </p> 37 * <p><pre><code> 38 * public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> { 39 * // The one way remote method to call. 40 * private final IRemoteInterface mTarget; 41 * 42 * // One way callback invoked when the remote method is done. 43 * private final IRemoteCallback mCallback = new IRemoteCallback.Stub() { 44 * public void onCompleted(Object result, int sequence) { 45 * onRemoteMethodResult(result, sequence); 46 * } 47 * }; 48 * 49 * public MyMethodCaller(IRemoteInterface target) { 50 * mTarget = target; 51 * } 52 * 53 * public Object onCallMyMethod(Object arg) throws RemoteException { 54 * final int sequence = onBeforeRemoteCall(); 55 * mTarget.myMethod(arg, sequence); 56 * return getResultTimed(sequence); 57 * } 58 * } 59 * </code></pre></p> 60 * 61 * @param <T> The type of the expected result. 62 * 63 * @hide 64 */ 65 public abstract class TimedRemoteCaller<T> { 66 67 public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000; 68 69 private final Object mLock = new Object(); 70 71 private final long mCallTimeoutMillis; 72 73 /** The callbacks we are waiting for, key == sequence id, value == 1 */ 74 @GuardedBy("mLock") 75 private final SparseIntArray mAwaitedCalls = new SparseIntArray(1); 76 77 /** The callbacks we received but for which the result has not yet been reported */ 78 @GuardedBy("mLock") 79 private final SparseArray<T> mReceivedCalls = new SparseArray<>(1); 80 81 @GuardedBy("mLock") 82 private int mSequenceCounter; 83 84 /** 85 * Create a new timed caller. 86 * 87 * @param callTimeoutMillis The time to wait in {@link #getResultTimed} before a timed call will 88 * be declared timed out 89 */ TimedRemoteCaller(long callTimeoutMillis)90 public TimedRemoteCaller(long callTimeoutMillis) { 91 mCallTimeoutMillis = callTimeoutMillis; 92 } 93 94 /** 95 * Indicate that a timed call will be made. 96 * 97 * @return The sequence id for the call 98 */ onBeforeRemoteCall()99 protected final int onBeforeRemoteCall() { 100 synchronized (mLock) { 101 int sequenceId; 102 do { 103 sequenceId = mSequenceCounter++; 104 } while (mAwaitedCalls.get(sequenceId) != 0); 105 106 mAwaitedCalls.put(sequenceId, 1); 107 108 return sequenceId; 109 } 110 } 111 112 /** 113 * Indicate that the timed call has returned. 114 * 115 * @param result The result of the timed call 116 * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()}) 117 */ onRemoteMethodResult(T result, int sequence)118 protected final void onRemoteMethodResult(T result, int sequence) { 119 synchronized (mLock) { 120 // Do nothing if we do not await the call anymore as it must have timed out 121 boolean containedSequenceId = mAwaitedCalls.get(sequence) != 0; 122 if (containedSequenceId) { 123 mAwaitedCalls.delete(sequence); 124 mReceivedCalls.put(sequence, result); 125 mLock.notifyAll(); 126 } 127 } 128 } 129 130 /** 131 * Wait until the timed call has returned. 132 * 133 * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()}) 134 * 135 * @return The result of the timed call (set in {@link #onRemoteMethodResult(Object, int)}) 136 */ getResultTimed(int sequence)137 protected final T getResultTimed(int sequence) throws TimeoutException { 138 final long startMillis = SystemClock.uptimeMillis(); 139 while (true) { 140 try { 141 synchronized (mLock) { 142 if (mReceivedCalls.indexOfKey(sequence) >= 0) { 143 return mReceivedCalls.removeReturnOld(sequence); 144 } 145 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; 146 final long waitMillis = mCallTimeoutMillis - elapsedMillis; 147 if (waitMillis <= 0) { 148 mAwaitedCalls.delete(sequence); 149 throw new TimeoutException("No response for sequence: " + sequence); 150 } 151 mLock.wait(waitMillis); 152 } 153 } catch (InterruptedException ie) { 154 /* ignore */ 155 } 156 } 157 } 158 } 159