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