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 
21 import 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  */
65 public 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 
TimedRemoteCaller(long callTimeoutMillis)83     public TimedRemoteCaller(long callTimeoutMillis) {
84         mCallTimeoutMillis = callTimeoutMillis;
85     }
86 
onBeforeRemoteCall()87     public final int onBeforeRemoteCall() {
88         synchronized (mLock) {
89             mAwaitedSequence = mSequenceCounter++;
90             return mAwaitedSequence;
91         }
92     }
93 
getResultTimed(int sequence)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 
onRemoteMethodResult(T result, int sequence)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 
waitForResultTimedLocked(int sequence)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