Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2015 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 com.android.camera.device;
     18 
     19 import com.android.camera.async.Lifetime;
     20 import com.android.camera.async.SafeCloseable;
     21 import com.android.camera.debug.Log.Tag;
     22 import com.android.camera.debug.Logger;
     23 
     24 import java.util.concurrent.locks.ReentrantLock;
     25 
     26 import javax.annotation.Nullable;
     27 import javax.annotation.ParametersAreNonnullByDefault;
     28 import javax.annotation.concurrent.GuardedBy;
     29 import javax.annotation.concurrent.ThreadSafe;
     30 
     31 /**
     32  * Internal state machine for dealing with the interactions of opening
     33  * a physical device. Since there are 4 device states and 2 target
     34  * states the transition table looks like this:
     35  *
     36  * Device  | Target
     37  * Opening   Opened -> Nothing.
     38  * Opened    Opened -> Execute onDeviceOpened.
     39  * Closing   Opened -> Nothing.
     40  * Closed    Opened -> Execute onDeviceOpening.
     41  *                     Device moves to Opening.
     42  * Opening   Closed -> Nothing.
     43  * Opened    Closed -> Execute onDeviceClosing.
     44  *                     Device moves to Closing.
     45  * Closing   Closed -> Nothing.
     46  * Closed    Closed -> Execute onDeviceClosed.
     47  *
     48  */
     49 @ThreadSafe
     50 @ParametersAreNonnullByDefault
     51 public class SingleDeviceStateMachine<TDevice, TKey> implements SingleDeviceCloseListener,
     52       SingleDeviceOpenListener<TDevice> {
     53     private static final Tag TAG = new Tag("DeviceStateM");
     54 
     55     /** Physical state of the device. */
     56     private enum DeviceState {
     57         OPENING,
     58         OPENED,
     59         CLOSING,
     60         CLOSED
     61     }
     62 
     63     /** Physical state the state machine should reach. */
     64     private enum TargetState {
     65         OPENED,
     66         CLOSED
     67     }
     68 
     69     private final ReentrantLock mLock;
     70     private final Lifetime mDeviceLifetime;
     71     private final SingleDeviceActions<TDevice> mDeviceActions;
     72     private final SingleDeviceShutdownListener<TKey> mShutdownListener;
     73     private final TKey mDeviceKey;
     74     private final Logger mLogger;
     75 
     76     @GuardedBy("mLock")
     77     private boolean mIsShutdown;
     78 
     79     @GuardedBy("mLock")
     80     private TargetState mTargetState;
     81 
     82     @GuardedBy("mLock")
     83     private DeviceState mDeviceState;
     84 
     85     @Nullable
     86     @GuardedBy("mLock")
     87     private SingleDeviceRequest<TDevice> mDeviceRequest;
     88 
     89     @Nullable
     90     @GuardedBy("mLock")
     91     private TDevice mOpenDevice;
     92 
     93     /**
     94      * This creates a new state machine with a listener to represent
     95      * the physical states of a device. Both the target and current
     96      * state of the device are initially set to "Closed"
     97      */
     98     public SingleDeviceStateMachine(SingleDeviceActions<TDevice> deviceActions,
     99           TKey deviceKey, SingleDeviceShutdownListener<TKey> deviceShutdownListener,
    100           Logger.Factory logFactory)  {
    101         mDeviceActions = deviceActions;
    102         mShutdownListener = deviceShutdownListener;
    103         mDeviceKey = deviceKey;
    104 
    105         mLock = new ReentrantLock();
    106         mDeviceLifetime = new Lifetime();
    107         mLogger = logFactory.create(TAG);
    108 
    109         mIsShutdown = false;
    110         mTargetState = TargetState.CLOSED;
    111         mDeviceState = DeviceState.CLOSED;
    112     }
    113 
    114     /**
    115      * Request that the state machine move towards an open state.
    116      */
    117     public void requestOpen() {
    118         mLock.lock();
    119         try {
    120             if (mIsShutdown) {
    121                return;
    122             }
    123 
    124             mTargetState = TargetState.OPENED;
    125             update();
    126         } finally {
    127             mLock.unlock();
    128         }
    129     }
    130 
    131     /**
    132      * Request that the state machine move towards a closed state.
    133      */
    134     public void requestClose() {
    135         mLock.lock();
    136         try {
    137             if (mIsShutdown) {
    138                 return;
    139             }
    140 
    141             mTargetState = TargetState.CLOSED;
    142             update();
    143         } finally {
    144             mLock.unlock();
    145         }
    146     }
    147 
    148     /**
    149      * When a new request is set, the previous request should be canceled
    150      * if it has not been completed.
    151      */
    152     public void setRequest(final SingleDeviceRequest<TDevice> deviceRequest) {
    153         mLock.lock();
    154         try {
    155             if (mIsShutdown) {
    156                 deviceRequest.close();
    157                 return;
    158             }
    159 
    160             SingleDeviceRequest<TDevice> previous = mDeviceRequest;
    161             mDeviceRequest = deviceRequest;
    162             mDeviceLifetime.add(deviceRequest);
    163             deviceRequest.getLifetime().add(new SafeCloseable() {
    164                     @Override
    165                     public void close() {
    166                         requestCloseIfCurrentRequest(deviceRequest);
    167                     }
    168                 });
    169 
    170             if (mOpenDevice != null) {
    171                 mDeviceRequest.set(mOpenDevice);
    172             }
    173 
    174             if (previous != null) {
    175                 previous.close();
    176             }
    177         } finally {
    178             mLock.unlock();
    179         }
    180     }
    181 
    182     @Override
    183     public void onDeviceOpened(TDevice device) {
    184         mLock.lock();
    185         try {
    186             if (mIsShutdown) {
    187                 return;
    188             }
    189 
    190             mOpenDevice = device;
    191             mDeviceState = DeviceState.OPENED;
    192 
    193             update();
    194         } finally {
    195             mLock.unlock();
    196         }
    197     }
    198 
    199     @Override
    200     public void onDeviceOpenException(Throwable throwable) {
    201         mLock.lock();
    202         try {
    203             if (mIsShutdown) {
    204                 return;
    205             }
    206 
    207             closeRequestWithException(throwable);
    208             shutdown();
    209         } finally {
    210             mLock.unlock();
    211         }
    212     }
    213 
    214     @Override
    215     public void onDeviceOpenException(TDevice tDevice) {
    216         mLock.lock();
    217         try {
    218             if (mIsShutdown) {
    219                 return;
    220             }
    221 
    222             closeRequestWithException(new CameraOpenException(-1));
    223             mDeviceState = DeviceState.CLOSING;
    224             mTargetState = TargetState.CLOSED;
    225             executeClose(tDevice);
    226         } finally {
    227             mLock.unlock();
    228         }
    229     }
    230 
    231     @Override
    232     public void onDeviceClosed() {
    233         mLock.lock();
    234         try {
    235             if (mIsShutdown) {
    236                 return;
    237             }
    238 
    239             mOpenDevice = null;
    240             mDeviceState = DeviceState.CLOSED;
    241 
    242             update();
    243         } finally {
    244             mLock.unlock();
    245         }
    246     }
    247 
    248     @Override
    249     public void onDeviceClosingException(Throwable throwable) {
    250         mLock.lock();
    251         try {
    252             if (mIsShutdown) {
    253                 return;
    254             }
    255 
    256             closeRequestWithException(throwable);
    257             shutdown();
    258         } finally {
    259             mLock.unlock();
    260         }
    261     }
    262 
    263 
    264     @GuardedBy("mLock")
    265     private void update() {
    266         if (mIsShutdown) {
    267             return;
    268         }
    269 
    270         if (mDeviceState == DeviceState.CLOSED && mTargetState == TargetState.OPENED) {
    271             executeOpen();
    272         } else if (mDeviceState == DeviceState.OPENED && mTargetState == TargetState.OPENED) {
    273             executeOpened();
    274         } else if (mDeviceState == DeviceState.OPENED && mTargetState == TargetState.CLOSED) {
    275             executeClose();
    276         }  else if (mDeviceState == DeviceState.CLOSED && mTargetState == TargetState.CLOSED) {
    277             shutdown();
    278         }
    279     }
    280 
    281     @GuardedBy("mLock")
    282     private void executeOpen() {
    283         mDeviceState = DeviceState.OPENING;
    284         try {
    285             mDeviceActions.executeOpen(this, mDeviceLifetime);
    286         } catch (Exception e) {
    287             onDeviceOpenException(e);
    288         }
    289         // TODO: Consider adding a timeout to the open call so that requests
    290         // are not left un-resolved.
    291     }
    292 
    293     @GuardedBy("mLock")
    294     private void executeOpened() {
    295         if(mDeviceRequest != null) {
    296             mDeviceRequest.set(mOpenDevice);
    297         }
    298 
    299         // TODO: Consider performing a shutdown if there is no open
    300         // device request.
    301     }
    302 
    303     @GuardedBy("mLock")
    304     private void executeClose() {
    305         // TODO: Consider adding a timeout to the close call so that requests
    306         // are not left un-resolved.
    307 
    308         final TDevice device = mOpenDevice;
    309         mOpenDevice = null;
    310 
    311         executeClose(device);
    312     }
    313 
    314     @GuardedBy("mLock")
    315     private void executeClose(@Nullable TDevice device) {
    316         if (device != null) {
    317             mDeviceState = DeviceState.CLOSING;
    318             mTargetState = TargetState.CLOSED;
    319             closeRequest();
    320 
    321             try {
    322                 mDeviceActions.executeClose(this, device);
    323             } catch (Exception e) {
    324                 onDeviceClosingException(e);
    325             }
    326         } else {
    327             shutdown();
    328         }
    329     }
    330 
    331     @GuardedBy("mLock")
    332     private void requestCloseIfCurrentRequest(SingleDeviceRequest<TDevice> request) {
    333         if (mDeviceRequest == null || mDeviceRequest == request) {
    334             requestClose();
    335         }
    336     }
    337 
    338     @GuardedBy("mLock")
    339     private void closeRequestWithException(Throwable exception) {
    340         mOpenDevice = null;
    341         if (mDeviceRequest != null) {
    342             mLogger.w("There was a problem closing device: " + mDeviceKey, exception);
    343 
    344             mDeviceRequest.closeWithException(exception);
    345             mDeviceRequest = null;
    346         }
    347     }
    348 
    349     @GuardedBy("mLock")
    350     private void closeRequest() {
    351         if (mDeviceRequest != null) {
    352             mDeviceRequest.close();
    353         }
    354         mDeviceRequest = null;
    355     }
    356 
    357     /**
    358      * Cancel requests, and set internal device state back to
    359      * a clean set of values.
    360      */
    361     private void shutdown() {
    362         mLock.lock();
    363         try {
    364             if (!mIsShutdown) {
    365                 mIsShutdown = true;
    366                 mLogger.i("Shutting down the device lifecycle for: " + mDeviceKey);
    367                 mOpenDevice = null;
    368                 mDeviceState = DeviceState.CLOSED;
    369                 mTargetState = TargetState.CLOSED;
    370 
    371                 closeRequest();
    372                 mDeviceLifetime.close();
    373                 mShutdownListener.onShutdown(mDeviceKey);
    374             } else {
    375                 mLogger.w("Shutdown was called multiple times!");
    376             }
    377         } finally {
    378             mLock.unlock();
    379         }
    380     }
    381 }
    382