• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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