Home | History | Annotate | Download | only in util
      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      */
     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      */
     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      */
    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      */
    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