Java程序  |  159行  |  5.4 KB

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;

import java.util.concurrent.TimeoutException;

/**
 * This is a helper class for making an async one way call and
 * its async one way response response in a sync fashion within
 * a timeout. The key idea is to call the remote method with a
 * sequence number and a callback and then starting to wait for
 * the response. The remote method calls back with the result and
 * the sequence number. If the response comes within the timeout
 * and its sequence number is the one sent in the method invocation,
 * then the call succeeded. If the response does not come within
 * the timeout then the call failed.
 * <p>
 * Typical usage is:
 * </p>
 * <p><pre><code>
 * public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> {
 *     // The one way remote method to call.
 *     private final IRemoteInterface mTarget;
 *
 *     // One way callback invoked when the remote method is done.
 *     private final IRemoteCallback mCallback = new IRemoteCallback.Stub() {
 *         public void onCompleted(Object result, int sequence) {
 *             onRemoteMethodResult(result, sequence);
 *         }
 *     };
 *
 *     public MyMethodCaller(IRemoteInterface target) {
 *         mTarget = target;
 *     }
 *
 *     public Object onCallMyMethod(Object arg) throws RemoteException {
 *         final int sequence = onBeforeRemoteCall();
 *         mTarget.myMethod(arg, sequence);
 *         return getResultTimed(sequence);
 *     }
 * }
 * </code></pre></p>
 *
 * @param <T> The type of the expected result.
 *
 * @hide
 */
public abstract class TimedRemoteCaller<T> {

    public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000;

    private final Object mLock = new Object();

    private final long mCallTimeoutMillis;

    /** The callbacks we are waiting for, key == sequence id, value == 1 */
    @GuardedBy("mLock")
    private final SparseIntArray mAwaitedCalls = new SparseIntArray(1);

    /** The callbacks we received but for which the result has not yet been reported */
    @GuardedBy("mLock")
    private final SparseArray<T> mReceivedCalls = new SparseArray<>(1);

    @GuardedBy("mLock")
    private int mSequenceCounter;

    /**
     * Create a new timed caller.
     *
     * @param callTimeoutMillis The time to wait in {@link #getResultTimed} before a timed call will
     *                          be declared timed out
     */
    public TimedRemoteCaller(long callTimeoutMillis) {
        mCallTimeoutMillis = callTimeoutMillis;
    }

    /**
     * Indicate that a timed call will be made.
     *
     * @return The sequence id for the call
     */
    protected final int onBeforeRemoteCall() {
        synchronized (mLock) {
            int sequenceId;
            do {
                sequenceId = mSequenceCounter++;
            } while (mAwaitedCalls.get(sequenceId) != 0);

            mAwaitedCalls.put(sequenceId, 1);

            return sequenceId;
        }
    }

    /**
     * Indicate that the timed call has returned.
     *
     * @param result The result of the timed call
     * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()})
     */
    protected final void onRemoteMethodResult(T result, int sequence) {
        synchronized (mLock) {
            // Do nothing if we do not await the call anymore as it must have timed out
            boolean containedSequenceId = mAwaitedCalls.get(sequence) != 0;
            if (containedSequenceId) {
                mAwaitedCalls.delete(sequence);
                mReceivedCalls.put(sequence, result);
                mLock.notifyAll();
            }
        }
    }

    /**
     * Wait until the timed call has returned.
     *
     * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()})
     *
     * @return The result of the timed call (set in {@link #onRemoteMethodResult(Object, int)})
     */
    protected final T getResultTimed(int sequence) throws TimeoutException {
        final long startMillis = SystemClock.uptimeMillis();
        while (true) {
            try {
                synchronized (mLock) {
                    if (mReceivedCalls.indexOfKey(sequence) >= 0) {
                        return mReceivedCalls.removeReturnOld(sequence);
                    }
                    final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
                    final long waitMillis = mCallTimeoutMillis - elapsedMillis;
                    if (waitMillis <= 0) {
                        mAwaitedCalls.delete(sequence);
                        throw new TimeoutException("No response for sequence: " + sequence);
                    }
                    mLock.wait(waitMillis);
                }
            } catch (InterruptedException ie) {
                /* ignore */
            }
        }
    }
}