diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index edb576249..385201f25 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -121,11 +121,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { countDownLatch.countDown(); } + @Override + public void onSleepTimerAlmostExpired() { + + } + @Override public void onSleepTimerExpired() { } + @Override + public void onSleepTimerReset() { + + } + @Override public void onWidgetUpdaterTick() { @@ -169,11 +179,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } + @Override + public void onSleepTimerAlmostExpired() { + + } + @Override public void onSleepTimerExpired() { } + @Override + public void onSleepTimerReset() { + + } + @Override public void onWidgetUpdaterTick() { countDownLatch.countDown(); @@ -221,7 +241,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); pstm.startWidgetUpdater(); pstm.startPositionSaver(); - pstm.setSleepTimer(100000); + pstm.setSleepTimer(100000, false, false); pstm.cancelAllTasks(); assertFalse(pstm.isPositionSaverActive()); assertFalse(pstm.isWidgetUpdaterActive()); @@ -240,6 +260,11 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } + @Override + public void onSleepTimerAlmostExpired() { + + } + @Override public void onSleepTimerExpired() { if (countDownLatch.getCount() == 0) { @@ -248,6 +273,11 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { countDownLatch.countDown(); } + @Override + public void onSleepTimerReset() { + + } + @Override public void onWidgetUpdaterTick() { @@ -258,7 +288,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } }); - pstm.setSleepTimer(TIME); + pstm.setSleepTimer(TIME, false, false); countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); pstm.shutdown(); } @@ -274,11 +304,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } + @Override + public void onSleepTimerAlmostExpired() { + + } + @Override public void onSleepTimerExpired() { fail("Sleeptimer expired"); } + @Override + public void onSleepTimerReset() { + + } + @Override public void onWidgetUpdaterTick() { @@ -289,7 +329,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } }); - pstm.setSleepTimer(TIME); + pstm.setSleepTimer(TIME, false, false); pstm.disableSleepTimer(); assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)); pstm.shutdown(); @@ -298,7 +338,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { public void testIsSleepTimerActivePositive() { final Context c = getInstrumentation().getTargetContext(); PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); - pstm.setSleepTimer(10000); + pstm.setSleepTimer(10000, false, false); assertTrue(pstm.isSleepTimerActive()); pstm.shutdown(); } @@ -306,7 +346,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { public void testIsSleepTimerActiveNegative() { final Context c = getInstrumentation().getTargetContext(); PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); - pstm.setSleepTimer(10000); + pstm.setSleepTimer(10000, false, false); pstm.disableSleepTimer(); assertFalse(pstm.isSleepTimerActive()); pstm.shutdown(); @@ -318,11 +358,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { } + @Override + public void onSleepTimerAlmostExpired() { + + } + @Override public void onSleepTimerExpired() { } + @Override + public void onSleepTimerReset() { + + } + @Override public void onWidgetUpdaterTick() { diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index f5b000abf..47bd8b666 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + 0, "Waiting time <= 0"); - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) - + " milliseconds"); + Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds"); if (isSleepTimerActive()) { sleepTimerFuture.cancel(true); } - sleepTimer = new SleepTimer(waitingTime); + sleepTimer = new SleepTimer(waitingTime, shakeToReset, vibrate); sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS); } @@ -216,7 +213,11 @@ public class PlaybackServiceTaskManager { * Returns true if the sleep timer is currently active. */ public synchronized boolean isSleepTimerActive() { - return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting; + return sleepTimer != null + && sleepTimerFuture != null + && !sleepTimerFuture.isCancelled() + && !sleepTimerFuture.isDone() + && sleepTimer.getWaitingTime() > 0; } /** @@ -224,8 +225,7 @@ public class PlaybackServiceTaskManager { */ public synchronized void disableSleepTimer() { if (isSleepTimerActive()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Disabling sleep timer"); + Log.d(TAG, "Disabling sleep timer"); sleepTimerFuture.cancel(true); } } @@ -255,7 +255,7 @@ public class PlaybackServiceTaskManager { public synchronized void cancelWidgetUpdater() { if (isWidgetUpdaterActive()) { widgetUpdaterFuture.cancel(false); - if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater"); + Log.d(TAG, "Cancelled WidgetUpdater"); } } @@ -284,16 +284,14 @@ public class PlaybackServiceTaskManager { Runnable chapterLoader = new Runnable() { @Override public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Chapter loader started"); + Log.d(TAG, "Chapter loader started"); if (media.getChapters() == null) { media.loadChapterMarks(); if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) { callback.onChapterLoaded(media); } } - if (BuildConfig.DEBUG) - Log.d(TAG, "Chapter loader stopped"); + Log.d(TAG, "Chapter loader stopped"); } }; chapterLoaderFuture = schedExecutor.submit(chapterLoader); @@ -324,63 +322,88 @@ public class PlaybackServiceTaskManager { /** * Sleeps for a given time and then pauses playback. */ - private class SleepTimer implements Runnable { + protected class SleepTimer implements Runnable { private static final String TAG = "SleepTimer"; - private static final long UPDATE_INTERVALL = 1000L; - private volatile long waitingTime; - private volatile boolean isWaiting; + private static final long UPDATE_INTERVAL = 1000L; + private static final long NOTIFICATION_THRESHOLD = 10000; + private long waitingTime; + private final boolean shakeToReset; + private final boolean vibrate; + private ShakeListener shakeListener; - public SleepTimer(long waitingTime) { + public SleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) { super(); this.waitingTime = waitingTime; - isWaiting = true; + this.shakeToReset = shakeToReset; + this.vibrate = vibrate; } @Override public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting"); + Log.d(TAG, "Starting"); + boolean notifiedAlmostExpired = false; + long lastTick = System.currentTimeMillis(); while (waitingTime > 0) { try { - Thread.sleep(UPDATE_INTERVALL); - waitingTime -= UPDATE_INTERVALL; + Thread.sleep(UPDATE_INTERVAL); + long now = System.currentTimeMillis(); + waitingTime -= now - lastTick; + lastTick = now; + Log.d(TAG, "time left: " + waitingTime); + + if(waitingTime < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) { + Log.d(TAG, "Sleep timer is about to expire"); + if(vibrate) { + Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if(v != null) { + v.vibrate(500); + } + } + if(shakeListener == null && shakeToReset) { + shakeListener = new ShakeListener(context, this); + } + callback.onSleepTimerAlmostExpired(); + notifiedAlmostExpired = true; + } if (waitingTime <= 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Waiting completed"); - postExecute(); + Log.d(TAG, "Sleep timer expired"); + shakeListener.pause(); + shakeListener = null; if (!Thread.currentThread().isInterrupted()) { callback.onSleepTimerExpired(); } - } } catch (InterruptedException e) { Log.d(TAG, "Thread was interrupted while waiting"); + e.printStackTrace(); break; } } - postExecute(); - } - - protected void postExecute() { - isWaiting = false; } public long getWaitingTime() { return waitingTime; } - public boolean isWaiting() { - return isWaiting; + public void onShake() { + setSleepTimer(15 * 60 * 1000, shakeToReset, vibrate); + callback.onSleepTimerReset(); + shakeListener.pause(); + shakeListener = null; } } - public static interface PSTMCallback { + public interface PSTMCallback { void positionSaverTick(); + void onSleepTimerAlmostExpired(); + void onSleepTimerExpired(); + void onSleepTimerReset(); + void onWidgetUpdaterTick(); void onChapterLoaded(Playable media); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java new file mode 100644 index 000000000..77d765a85 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java @@ -0,0 +1,63 @@ +package de.danoeh.antennapod.core.service.playback; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.FloatMath; +import android.util.Log; + +public class ShakeListener implements SensorEventListener +{ + private static final String TAG = ShakeListener.class.getSimpleName(); + + private Sensor mAccelerometer; + private SensorManager mSensorMgr; + private PlaybackServiceTaskManager.SleepTimer mSleepTimer; + private Context mContext; + + public ShakeListener(Context context, PlaybackServiceTaskManager.SleepTimer sleepTimer) { + mContext = context; + mSleepTimer = sleepTimer; + resume(); + } + + public void resume() { + mSensorMgr = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + if (mSensorMgr == null) { + throw new UnsupportedOperationException("Sensors not supported"); + } + mAccelerometer = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (!mSensorMgr.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI)) { // if not supported + mSensorMgr.unregisterListener(this); + throw new UnsupportedOperationException("Accelerometer not supported"); + } + } + + public void pause() { + if (mSensorMgr != null) { + mSensorMgr.unregisterListener(this); + mSensorMgr = null; + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + float gX = event.values[0] / SensorManager.GRAVITY_EARTH; + float gY = event.values[1] / SensorManager.GRAVITY_EARTH; + float gZ = event.values[2] / SensorManager.GRAVITY_EARTH; + + float gForce = FloatMath.sqrt(gX*gX + gY*gY + gZ*gZ); + if (gForce > 2.25f) { + Log.d(TAG, "Detected shake " + gForce); + mSleepTimer.onShake(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + return; + } + +} \ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index ba5428b81..42aa3b713 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -652,9 +652,9 @@ public abstract class PlaybackController { } } - public void setSleepTimer(long time) { + public void setSleepTimer(long time, boolean shakeToReset, boolean vibrate) { if (playbackService != null) { - playbackService.setSleepTimer(time); + playbackService.setSleepTimer(time, shakeToReset, vibrate); } }