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 17package android.util; 18 19import android.os.SystemClock; 20 21import java.util.concurrent.TimeoutException; 22 23/** 24 * This is a helper class for making an async one way call and 25 * its async one way response response in a sync fashion within 26 * a timeout. The key idea is to call the remote method with a 27 * sequence number and a callback and then starting to wait for 28 * the response. The remote method calls back with the result and 29 * the sequence number. If the response comes within the timeout 30 * and its sequence number is the one sent in the method invocation, 31 * then the call succeeded. If the response does not come within 32 * the timeout then the call failed. Older result received when 33 * waiting for the result are ignored. 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 */ 65public abstract class TimedRemoteCaller<T> { 66 67 public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000; 68 69 private static final int UNDEFINED_SEQUENCE = -1; 70 71 private final Object mLock = new Object(); 72 73 private final long mCallTimeoutMillis; 74 75 private int mSequenceCounter; 76 77 private int mReceivedSequence = UNDEFINED_SEQUENCE; 78 79 private int mAwaitedSequence = UNDEFINED_SEQUENCE; 80 81 private T mResult; 82 83 public TimedRemoteCaller(long callTimeoutMillis) { 84 mCallTimeoutMillis = callTimeoutMillis; 85 } 86 87 public final int onBeforeRemoteCall() { 88 synchronized (mLock) { 89 mAwaitedSequence = mSequenceCounter++; 90 return mAwaitedSequence; 91 } 92 } 93 94 public final T getResultTimed(int sequence) throws TimeoutException { 95 synchronized (mLock) { 96 final boolean success = waitForResultTimedLocked(sequence); 97 if (!success) { 98 throw new TimeoutException("No reponse for sequence: " + sequence); 99 } 100 T result = mResult; 101 mResult = null; 102 return result; 103 } 104 } 105 106 public final void onRemoteMethodResult(T result, int sequence) { 107 synchronized (mLock) { 108 if (sequence == mAwaitedSequence) { 109 mReceivedSequence = sequence; 110 mResult = result; 111 mLock.notifyAll(); 112 } 113 } 114 } 115 116 private boolean waitForResultTimedLocked(int sequence) { 117 final long startMillis = SystemClock.uptimeMillis(); 118 while (true) { 119 try { 120 if (mReceivedSequence == sequence) { 121 return true; 122 } 123 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; 124 final long waitMillis = mCallTimeoutMillis - elapsedMillis; 125 if (waitMillis <= 0) { 126 return false; 127 } 128 mLock.wait(waitMillis); 129 } catch (InterruptedException ie) { 130 /* ignore */ 131 } 132 } 133 } 134} 135