Vibrate and lower volume when timer is about to expire, shake to reset timer

This commit is contained in:
Martin Fietz 2015-08-09 11:42:43 +02:00
parent 946d5ef50c
commit 771b1e2a16
7 changed files with 222 additions and 51 deletions

View File

@ -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() {

View File

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"

View File

@ -404,12 +404,21 @@ public class PlaybackService extends Service {
saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL);
}
@Override
public void onSleepTimerAlmostExpired() {
mediaPlayer.setVolume(0.5f);
}
@Override
public void onSleepTimerExpired() {
mediaPlayer.pause(true, true);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}
@Override
public void onSleepTimerReset() {
mediaPlayer.setVolume(1.0f);
}
@Override
public void onWidgetUpdaterTick() {
@ -652,10 +661,9 @@ public class PlaybackService extends Service {
}
}
public void setSleepTimer(long waitingTime) {
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
+ " milliseconds");
taskManager.setSleepTimer(waitingTime);
public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}

View File

@ -619,6 +619,32 @@ public class PlaybackServiceMediaPlayer {
return retVal;
}
/**
* Sets the playback speed.
* This method is executed on an internal executor service.
*/
public void setVolume(final float volume) {
executor.submit(new Runnable() {
@Override
public void run() {
setVolumeSync(volume);
}
});
}
/**
* Sets the playback speed.
* This method is executed on the caller's thread.
*/
private void setVolumeSync(float volume) {
playerLock.lock();
if (media != null && media.getMediaType() == MediaType.AUDIO) {
mediaPlayer.setVolume(volume, volume);
Log.d(TAG, "Media player volume was set to " + volume);
}
playerLock.unlock();
}
public MediaType getCurrentMediaType() {
return mediaType;
}

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
import android.os.Vibrator;
import android.util.Log;
import org.apache.commons.lang3.Validate;
@ -14,12 +15,10 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.greenrobot.event.EventBus;
@ -168,7 +167,7 @@ public class PlaybackServiceTaskManager {
public synchronized void cancelPositionSaver() {
if (isPositionSaverActive()) {
positionSaverFuture.cancel(false);
if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
Log.d(TAG, "Cancelled PositionSaver");
}
}
@ -186,9 +185,9 @@ public class PlaybackServiceTaskManager {
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
if (BuildConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
Log.d(TAG, "Started WidgetUpdater");
} else {
if (BuildConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
Log.d(TAG, "Call to startWidgetUpdater was ignored.");
}
}
@ -199,16 +198,14 @@ public class PlaybackServiceTaskManager {
*
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
*/
public synchronized void setSleepTimer(long waitingTime) {
public synchronized void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
Validate.isTrue(waitingTime > 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);

View File

@ -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;
}
}

View File

@ -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);
}
}