Home | History | Annotate | Download | only in blocking
      1 /*
      2  * Copyright 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 package com.android.ex.camera2.blocking;
     17 
     18 import android.hardware.camera2.CameraDevice;
     19 import android.os.Handler;
     20 import android.os.SystemClock;
     21 import android.util.Log;
     22 
     23 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
     24 
     25 import java.util.Arrays;
     26 import java.util.Collection;
     27 import java.util.concurrent.LinkedBlockingQueue;
     28 import java.util.concurrent.TimeUnit;
     29 
     30 
     31 /**
     32  * A camera device listener that implements blocking operations on state changes.
     33  *
     34  * <p>Provides wait calls that block until the next unobserved state of the
     35  * requested type arrives. Unobserved states are states that have occurred since
     36  * the last wait, or that will be received from the camera device in the
     37  * future.</p>
     38  *
     39  * <p>Pass-through all StateCallback changes to the proxy.</p>
     40  *
     41  */
     42 public class BlockingStateCallback extends CameraDevice.StateCallback {
     43     private static final String TAG = "BlockingStateCallback";
     44     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     45 
     46     private final CameraDevice.StateCallback mProxy;
     47 
     48     // Guards mWaiting
     49     private final Object mLock = new Object();
     50     private boolean mWaiting = false;
     51 
     52     private final LinkedBlockingQueue<Integer> mRecentStates =
     53             new LinkedBlockingQueue<Integer>();
     54 
     55     private void setCurrentState(int state) {
     56         if (VERBOSE) Log.v(TAG, "Camera device state now " + stateToString(state));
     57         try {
     58             mRecentStates.put(state);
     59         } catch (InterruptedException e) {
     60             throw new RuntimeException("Unable to set device state", e);
     61         }
     62     }
     63 
     64     private static final String[] mStateNames = {
     65         "STATE_UNINITIALIZED",
     66         "STATE_OPENED",
     67         "STATE_CLOSED",
     68         "STATE_DISCONNECTED",
     69         "STATE_ERROR"
     70     };
     71 
     72     /**
     73      * Device has not reported any state yet
     74      */
     75     public static final int STATE_UNINITIALIZED = -1;
     76 
     77     /**
     78      * Device is in the first-opened state (transitory)
     79      */
     80     public static final int STATE_OPENED = 0;
     81 
     82     /**
     83      * Device is closed
     84      */
     85     public static final int STATE_CLOSED = 1;
     86 
     87     /**
     88      * Device is disconnected
     89      */
     90     public static final int STATE_DISCONNECTED = 2;
     91 
     92     /**
     93      * Device has encountered a fatal error
     94      */
     95     public static final int STATE_ERROR = 3;
     96 
     97     /**
     98      * Total number of reachable states
     99      */
    100     private static final int NUM_STATES = 4;
    101 
    102     public BlockingStateCallback() {
    103         mProxy = null;
    104     }
    105 
    106     public BlockingStateCallback(CameraDevice.StateCallback listener) {
    107         mProxy = listener;
    108     }
    109 
    110     @Override
    111     public void onOpened(CameraDevice camera) {
    112         if (mProxy != null) {
    113             mProxy.onOpened(camera);
    114         }
    115         setCurrentState(STATE_OPENED);
    116     }
    117 
    118     @Override
    119     public void onDisconnected(CameraDevice camera) {
    120         if (mProxy != null) {
    121             mProxy.onDisconnected(camera);
    122         }
    123         setCurrentState(STATE_DISCONNECTED);
    124     }
    125 
    126     @Override
    127     public void onError(CameraDevice camera, int error) {
    128         if (mProxy != null) {
    129             mProxy.onError(camera, error);
    130         }
    131         setCurrentState(STATE_ERROR);
    132     }
    133 
    134     @Override
    135     public void onClosed(CameraDevice camera) {
    136         if (mProxy != null) {
    137             mProxy.onClosed(camera);
    138         }
    139         setCurrentState(STATE_CLOSED);
    140     }
    141 
    142     /**
    143      * Wait until the desired state is observed, checking all state
    144      * transitions since the last state that was waited on.
    145      *
    146      * <p>Note: Only one waiter allowed at a time!</p>
    147      *
    148      * @param state state to observe a transition to
    149      * @param timeout how long to wait in milliseconds
    150      *
    151      * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
    152      */
    153     public void waitForState(int state, long timeout) {
    154         Integer[] stateArray = { state };
    155 
    156         waitForAnyOfStates(Arrays.asList(stateArray), timeout);
    157     }
    158 
    159     /**
    160      * Wait until the one of the desired states is observed, checking all
    161      * state transitions since the last state that was waited on.
    162      *
    163      * <p>Note: Only one waiter allowed at a time!</p>
    164      *
    165      * @param states Set of desired states to observe a transition to.
    166      * @param timeout how long to wait in milliseconds
    167      *
    168      * @return the state reached
    169      * @throws TimeoutRuntimeException if none of the states is observed before timeout.
    170      *
    171      */
    172     public int waitForAnyOfStates(Collection<Integer> states, final long timeout) {
    173         synchronized (mLock) {
    174             if (mWaiting) {
    175                 throw new IllegalStateException("Only one waiter allowed at a time");
    176             }
    177             mWaiting = true;
    178         }
    179         if (VERBOSE) {
    180             StringBuilder s = new StringBuilder("Waiting for state(s) ");
    181             appendStates(s, states);
    182             Log.v(TAG, s.toString());
    183         }
    184 
    185         Integer nextState = null;
    186         long timeoutLeft = timeout;
    187         long startMs = SystemClock.elapsedRealtime();
    188         try {
    189             while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS))
    190                     != null) {
    191                 if (VERBOSE) {
    192                     Log.v(TAG, "  Saw transition to " + stateToString(nextState));
    193                 }
    194                 if (states.contains(nextState)) break;
    195                 long endMs = SystemClock.elapsedRealtime();
    196                 timeoutLeft -= (endMs - startMs);
    197                 startMs = endMs;
    198             }
    199         } catch (InterruptedException e) {
    200             throw new UnsupportedOperationException("Does not support interrupts on waits", e);
    201         }
    202 
    203         synchronized (mLock) {
    204             mWaiting = false;
    205         }
    206 
    207         if (!states.contains(nextState)) {
    208             StringBuilder s = new StringBuilder("Timed out after ");
    209             s.append(timeout);
    210             s.append(" ms waiting for state(s) ");
    211             appendStates(s, states);
    212 
    213             throw new TimeoutRuntimeException(s.toString());
    214         }
    215 
    216         return nextState;
    217     }
    218 
    219     /**
    220      * Convert state integer to a String
    221      */
    222     public static String stateToString(int state) {
    223         return mStateNames[state + 1];
    224     }
    225 
    226     /**
    227      * Append all states to string
    228      */
    229     public static void appendStates(StringBuilder s, Collection<Integer> states) {
    230         boolean start = true;
    231         for (Integer state : states) {
    232             if (!start) s.append(" ");
    233             s.append(stateToString(state));
    234             start = false;
    235         }
    236     }
    237 }
    238