Back to project page HeadphoneController.
The source code is released under:
GNU General Public License
If you think the Android project HeadphoneController listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * TODO: license//from w ww .j a v a2 s.c o m */ package ca.mbabic.headphonecontroller.statemachine; import java.lang.Thread.State; import java.util.concurrent.Semaphore; import android.util.Log; import ca.mbabic.headphonecontroller.services.MediaButtonListenerService; /** * State machine keeping track of the state of the button presses. * Implementation of the State Pattern. See: * http://sourcemaking.com/design_patterns/state * * @author Marko Babic */ public class HCStateMachine { /** * Reference to MediaButtonListenerService such that after command execution * we can re-register ourselves as exclusive media button player listener. */ private static MediaButtonListenerService listenerService; /** * Class specific logging tag. */ private static final String TAG = ".statemachine.HCStateMachine"; /** * Interval to wait between button presses. */ // TODO: make configurable private static final long BTN_PRESS_INTERVAL = 400; /** * Instance of HCStateMachine available to the application. */ private static volatile HCStateMachine instance = null; /** * Reference to the state in which the machine is currently in. */ private static HCState currentState; /** * The starting state of the machine. */ private static HCState startState; /** * Time left until count down thread will execute the current state's * command. */ private static long timeToExecution; /** * The amount of time the count down thread will sleep between acquiring the * semaphore and updating timeToExecution. */ private static final long SLEEP_INTERVAL = 100; /** * Semaphore used to lock access to shared variable timeToExecution. */ private static Semaphore countDownSemaphore = new Semaphore(1); /** * Thread in which the count down to execution monitoring functionality is * implemented. */ private Thread countDownThread; static Runnable countDownRunnable = new Runnable() { public void run() { timeToExecution = BTN_PRESS_INTERVAL; while (timeToExecution > 0) { try { Thread.sleep(SLEEP_INTERVAL); countDownSemaphore.acquire(); timeToExecution -= SLEEP_INTERVAL; } catch (InterruptedException ie) { Log.e(TAG, "Coundown thread interrupted!"); return; } finally { countDownSemaphore.release(); } } // Count down expired, execute current state's command. // TODO: this should be synchronized ... and we should take one // last check here to make sure timeToExecution <= 0 and if not // start looping again and not execute anything ... currentState.executeCommand(); listenerService.registerAsMediaButtonListener(); currentState = startState; } }; /** * Private constructor. Get global application reference to state machine * via getInstance(). */ private HCStateMachine() { startState = new InactiveState(); currentState = startState; } public void setService(MediaButtonListenerService mbls) { listenerService = mbls; } /** * @return Singleton instance of HCStateMachine. */ public static HCStateMachine getInstance() { if (instance == null) { synchronized (HCStateMachine.class) { // Double check instance locking if (instance == null) { instance = new HCStateMachine(); } } } return instance; } /** * Indicate to state machine that the media button has been pressed on the * headphones. TODO: keep track of long clicks... should not transition to * next state at end of such a command. */ public void keyUp() { makeStateTransition(); } /** * Transition to next state as appropriate given currentState. */ private void makeStateTransition() { HCState nextState = currentState.getNextState(); if (currentState.isTerminal()) { stopCountDownToExecution(); currentState.executeCommand(); currentState = startState; } else { currentState = nextState; startCountDownToExecution(); } } /** * Call to start or reset a count down to execution of the current state's * command. */ private void startCountDownToExecution() { try { countDownSemaphore.acquire(); if (countDownThread == null || countDownThread.getState() == State.TERMINATED) { countDownThread = new Thread(countDownRunnable); countDownThread.start(); } else { timeToExecution = BTN_PRESS_INTERVAL; } } catch (Exception e) { Log.e(TAG, e.toString()); } finally { countDownSemaphore.release(); } } /** * Called to cancel count down to execution thread. */ private void stopCountDownToExecution() { countDownThread.interrupt(); try { countDownThread.join(); } catch (Exception e) { Log.e(TAG, e.toString()); } } }