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 */ SingleDeviceStateMachine(SingleDeviceActions<TDevice> deviceActions, TKey deviceKey, SingleDeviceShutdownListener<TKey> deviceShutdownListener, Logger.Factory logFactory)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 */ requestOpen()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 */ requestClose()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 */ setRequest(final SingleDeviceRequest<TDevice> deviceRequest)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 onDeviceOpened(TDevice device)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 onDeviceOpenException(Throwable throwable)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 onDeviceOpenException(TDevice tDevice)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 onDeviceClosed()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 onDeviceClosingException(Throwable throwable)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") update()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") executeOpen()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") executeOpened()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") executeClose()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") executeClose(@ullable TDevice device)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") requestCloseIfCurrentRequest(SingleDeviceRequest<TDevice> request)332 private void requestCloseIfCurrentRequest(SingleDeviceRequest<TDevice> request) { 333 if (mDeviceRequest == null || mDeviceRequest == request) { 334 requestClose(); 335 } 336 } 337 338 @GuardedBy("mLock") closeRequestWithException(Throwable exception)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") closeRequest()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 */ shutdown()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