summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikalacki Sava <mikalackis@gmail.com>2015-01-13 17:37:46 +0100
committerMikalacki Sava <mikalackis@gmail.com>2015-02-23 17:35:42 +0100
commit758ec43f27e07c329578e606db079dd56e9d4c2e (patch)
tree957456570b434b81ad99ae17704d684cc472fafe
parentfd06a775bb1668dc0df4de96f3c84ad1e9b5a459 (diff)
downloadandroid_packages_apps_Eleven-stable/cm-12.0-YNG1I.tar.gz
android_packages_apps_Eleven-stable/cm-12.0-YNG1I.tar.bz2
android_packages_apps_Eleven-stable/cm-12.0-YNG1I.zip
Eleven: shake to play next song, only available while music is playing.stable/cm-12.0-YNG1I
Allows user to shake his device to switch to next song. This feature is available through settings and is invoked only while music is playing. Change-Id: Ifb0866565d49443af7f3ac679e80601660506515
-rw-r--r--AndroidManifest.xml3
-rw-r--r--res/values/strings.xml2
-rw-r--r--res/xml/settings.xml7
-rw-r--r--src/com/cyanogenmod/eleven/IElevenService.aidl1
-rw-r--r--src/com/cyanogenmod/eleven/MusicPlaybackService.java82
-rw-r--r--src/com/cyanogenmod/eleven/ui/activities/SettingsActivity.java17
-rw-r--r--src/com/cyanogenmod/eleven/utils/MusicUtils.java16
-rw-r--r--src/com/cyanogenmod/eleven/utils/PreferenceUtils.java18
-rw-r--r--src/com/cyanogenmod/eleven/utils/ShakeDetector.java276
9 files changed, 420 insertions, 2 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 25381a3..ce19488 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -48,6 +48,9 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <!-- Accelerometer feature for shake to play -->
+ <uses-feature android:name="android.hardware.sensor.accelerometer" />
+
<application
android:name="com.cyanogenmod.eleven.ElevenApplication"
android:allowBackup="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5bf25b1..1c0c540 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -140,6 +140,8 @@
<string name="settings_show_music_visualization_title">Show music visualization</string>
<string name="settings_show_lyrics_title">Show song lyrics</string>
<string name="settings_show_lyrics_summary">For songs that have an srt file</string>
+ <string name="settings_shake_to_play">Shake To Play</string>
+ <string name="settings_shake_to_play_summary">Shake your device to play next song</string>
<!-- App widget -->
<string name="app_widget_small">Music: 4 \u00d7 1</string>
diff --git a/res/xml/settings.xml b/res/xml/settings.xml
index 05860ea..33151a4 100644
--- a/res/xml/settings.xml
+++ b/res/xml/settings.xml
@@ -51,6 +51,13 @@
android:key="show_lyrics"
android:title="@string/settings_show_lyrics_title"
android:summary="@string/settings_show_lyrics_summary"/>
+
+ <!-- Shake to switch songs -->
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="shake_to_play"
+ android:title="@string/settings_shake_to_play"
+ android:summary="@string/settings_shake_to_play_summary"/>
</PreferenceCategory>
<!-- Storage catetory -->
<PreferenceCategory android:title="@string/settings_storage_category" >
diff --git a/src/com/cyanogenmod/eleven/IElevenService.aidl b/src/com/cyanogenmod/eleven/IElevenService.aidl
index 82a7a97..21ac3ae 100644
--- a/src/com/cyanogenmod/eleven/IElevenService.aidl
+++ b/src/com/cyanogenmod/eleven/IElevenService.aidl
@@ -48,5 +48,6 @@ interface IElevenService
int getRepeatMode();
int getMediaMountedCount();
int getAudioSessionId();
+ void setShakeToPlayEnabled(boolean enabled);
}
diff --git a/src/com/cyanogenmod/eleven/MusicPlaybackService.java b/src/com/cyanogenmod/eleven/MusicPlaybackService.java
index 73c3694..6703dbf 100644
--- a/src/com/cyanogenmod/eleven/MusicPlaybackService.java
+++ b/src/com/cyanogenmod/eleven/MusicPlaybackService.java
@@ -31,6 +31,7 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
+import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaMetadata;
@@ -66,6 +67,8 @@ import com.cyanogenmod.eleven.provider.SongPlayCount;
import com.cyanogenmod.eleven.service.MusicPlaybackTrack;
import com.cyanogenmod.eleven.utils.BitmapWithColors;
import com.cyanogenmod.eleven.utils.Lists;
+import com.cyanogenmod.eleven.utils.PreferenceUtils;
+import com.cyanogenmod.eleven.utils.ShakeDetector;
import com.cyanogenmod.eleven.utils.SrtManager;
import java.io.File;
@@ -517,6 +520,25 @@ public class MusicPlaybackService extends Service {
private MusicPlaybackState mPlaybackStateStore;
/**
+ * Shake detector class used for shake to switch song feature
+ */
+ private ShakeDetector mShakeDetector;
+
+ private ShakeDetector.Listener mShakeDetectorListener=new ShakeDetector.Listener() {
+
+ @Override
+ public void hearShake() {
+ /*
+ * on shake detect, play next song
+ */
+ if (D) {
+ Log.d(TAG,"Shake detected!!!");
+ }
+ gotoNext(true);
+ }
+ };
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -551,6 +573,7 @@ public class MusicPlaybackService extends Service {
return true;
}
stopSelf(mServiceStartId);
+
return true;
}
@@ -738,6 +761,9 @@ public class MusicPlaybackService extends Service {
mUnmountReceiver = null;
}
+ // deinitialize shake detector
+ stopShakeDetector(true);
+
// Release the wake lock
mWakeLock.release();
}
@@ -2337,6 +2363,7 @@ public class MusicPlaybackService extends Service {
* Stops playback.
*/
public void stop() {
+ stopShakeDetector(false);
stop(true);
}
@@ -2344,6 +2371,7 @@ public class MusicPlaybackService extends Service {
* Resumes or starts playback.
*/
public void play() {
+ startShakeDetector();
play(true);
}
@@ -2402,6 +2430,7 @@ public class MusicPlaybackService extends Service {
if (mIsSupposedToBePlaying) {
mPlayer.pause();
setIsSupposedToBePlaying(false, true);
+ stopShakeDetector(false);
}
}
}
@@ -2718,6 +2747,51 @@ public class MusicPlaybackService extends Service {
notifyChange(PLAYLIST_CHANGED);
}
+ /**
+ * Called to set the status of shake to play feature
+ */
+ public void setShakeToPlayEnabled(boolean enabled) {
+ if (D) {
+ Log.d(TAG, "ShakeToPlay status: " + enabled);
+ }
+ if (enabled) {
+ if (mShakeDetector == null) {
+ mShakeDetector = new ShakeDetector(mShakeDetectorListener);
+ }
+ // if song is already playing, start listening immediately
+ if (isPlaying()) {
+ startShakeDetector();
+ }
+ }
+ else {
+ stopShakeDetector(true);
+ }
+ }
+
+ /**
+ * Called to start listening to shakes
+ */
+ private void startShakeDetector() {
+ if (mShakeDetector != null) {
+ mShakeDetector.start((SensorManager)getSystemService(SENSOR_SERVICE));
+ }
+ }
+
+ /**
+ * Called to stop listening to shakes
+ */
+ private void stopShakeDetector(final boolean destroyShakeDetector) {
+ if (mShakeDetector != null) {
+ mShakeDetector.stop();
+ }
+ if(destroyShakeDetector){
+ mShakeDetector = null;
+ if (D) {
+ Log.d(TAG, "ShakeToPlay destroyed!!!");
+ }
+ }
+ }
+
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
/**
* {@inheritDoc}
@@ -3625,6 +3699,14 @@ public class MusicPlaybackService extends Service {
return mService.get().getAudioSessionId();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setShakeToPlayEnabled(boolean enabled) {
+ mService.get().setShakeToPlayEnabled(enabled);
+ }
+
}
}
diff --git a/src/com/cyanogenmod/eleven/ui/activities/SettingsActivity.java b/src/com/cyanogenmod/eleven/ui/activities/SettingsActivity.java
index c4f8139..14209a3 100644
--- a/src/com/cyanogenmod/eleven/ui/activities/SettingsActivity.java
+++ b/src/com/cyanogenmod/eleven/ui/activities/SettingsActivity.java
@@ -16,14 +16,19 @@ package com.cyanogenmod.eleven.ui.activities;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceManager;
import android.view.MenuItem;
import com.cyanogenmod.eleven.R;
import com.cyanogenmod.eleven.cache.ImageFetcher;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PreferenceUtils;
/**
* Settings.
@@ -31,7 +36,7 @@ import com.cyanogenmod.eleven.cache.ImageFetcher;
* @author Andrew Neal (andrewdneal@gmail.com)
*/
@SuppressWarnings("deprecation")
-public class SettingsActivity extends PreferenceActivity {
+public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener{
/**
* {@inheritDoc}
@@ -50,6 +55,8 @@ public class SettingsActivity extends PreferenceActivity {
// Removes the cache entries
deleteCache();
+
+ PreferenceUtils.getInstance(this).setOnSharedPreferenceChangeListener(this);
}
/**
@@ -94,4 +101,12 @@ public class SettingsActivity extends PreferenceActivity {
}
});
}
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ if(key.equals(PreferenceUtils.SHAKE_TO_PLAY)){
+ MusicUtils.setShakeToPlayEnabled(sharedPreferences.getBoolean(key, false));
+ }
+ }
}
diff --git a/src/com/cyanogenmod/eleven/utils/MusicUtils.java b/src/com/cyanogenmod/eleven/utils/MusicUtils.java
index 441413a..252f869 100644
--- a/src/com/cyanogenmod/eleven/utils/MusicUtils.java
+++ b/src/com/cyanogenmod/eleven/utils/MusicUtils.java
@@ -87,6 +87,8 @@ public final class MusicUtils {
public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"
+ " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; //$NON-NLS-2$
+ private static boolean sShakeToPlayEnabled;
+
static {
mConnectionMap = new WeakHashMap<Context, ServiceBinder>();
sEmptyList = new long[0];
@@ -107,6 +109,7 @@ public final class MusicUtils {
if (realActivity == null) {
realActivity = (Activity)context;
}
+ sShakeToPlayEnabled = PreferenceUtils.getInstance(context).getShakeToPlay();
final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
contextWrapper.startService(new Intent(contextWrapper, MusicPlaybackService.class));
final ServiceBinder binder = new ServiceBinder(callback);
@@ -154,6 +157,7 @@ public final class MusicUtils {
if (mCallback != null) {
mCallback.onServiceConnected(className, service);
}
+ MusicUtils.setShakeToPlayEnabled(sShakeToPlayEnabled);
}
@Override
@@ -271,6 +275,18 @@ public final class MusicUtils {
}
/**
+ * Set shake to play status
+ */
+ public static void setShakeToPlayEnabled(boolean enabled) {
+ try {
+ if (mService != null) {
+ mService.setShakeToPlayEnabled(enabled);
+ }
+ } catch (final RemoteException ignored) {
+ }
+ }
+
+ /**
* Changes to the next track asynchronously
*/
public static void asyncNext(final Context context) {
diff --git a/src/com/cyanogenmod/eleven/utils/PreferenceUtils.java b/src/com/cyanogenmod/eleven/utils/PreferenceUtils.java
index 27823c3..e03cf2d 100644
--- a/src/com/cyanogenmod/eleven/utils/PreferenceUtils.java
+++ b/src/com/cyanogenmod/eleven/utils/PreferenceUtils.java
@@ -16,6 +16,7 @@ package com.cyanogenmod.eleven.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.preference.PreferenceManager;
import com.cyanogenmod.eleven.R;
@@ -77,6 +78,9 @@ public final class PreferenceUtils {
// show visualizer flag
public static final String SHOW_VISUALIZER = "music_visualization";
+ // shake to play flag
+ public static final String SHAKE_TO_PLAY = "shake_to_play";
+
private static PreferenceUtils sInstance;
private final SharedPreferences mPreferences;
@@ -119,6 +123,14 @@ public final class PreferenceUtils {
}
}, (Void[])null);
}
+
+ /**
+ * Set the listener for preference change
+ * @param listener
+ */
+ public void setOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener){
+ mPreferences.registerOnSharedPreferenceChangeListener(listener);
+ }
/**
* Returns the last page the user was on when the app was exited.
@@ -322,4 +334,8 @@ public final class PreferenceUtils {
public boolean getShowVisualizer() {
return mPreferences.getBoolean(SHOW_VISUALIZER, true);
}
-}
+
+ public boolean getShakeToPlay() {
+ return mPreferences.getBoolean(SHAKE_TO_PLAY, false);
+ }
+} \ No newline at end of file
diff --git a/src/com/cyanogenmod/eleven/utils/ShakeDetector.java b/src/com/cyanogenmod/eleven/utils/ShakeDetector.java
new file mode 100644
index 0000000..d0be70d
--- /dev/null
+++ b/src/com/cyanogenmod/eleven/utils/ShakeDetector.java
@@ -0,0 +1,276 @@
+
+package com.cyanogenmod.eleven.utils;
+
+/*
+ * Copyright 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Detects phone shaking. If > 75% of the samples taken in the past 0.5s are accelerating, the
+ * device is a) shaking, or b) free falling 1.84m (h = 1/2*g*t^2*3/4).
+ *
+ * @author Bob Lee (bob@squareup.com)
+ * @author Eric Burke (eric@squareup.com)
+ */
+public class ShakeDetector implements SensorEventListener {
+
+ /**
+ * When the magnitude of total acceleration exceeds this value, the phone is accelerating.
+ */
+ private static final int ACCELERATION_THRESHOLD = 13;
+
+ /**
+ * Minimum time between two consecutive shakes in milliseconds to invoke listener
+ */
+ private static final int MIN_TIME_BETWEEN_TWO_SHAKES = 1000;
+
+ private long mDetectedShakeStartTime = 0;
+
+ /** Listens for shakes. */
+ public interface Listener {
+ /** Called on the main thread when the device is shaken. */
+ void hearShake();
+ }
+
+ private final SampleQueue queue = new SampleQueue();
+ private final Listener listener;
+
+ private SensorManager sensorManager;
+ private Sensor accelerometer;
+
+ public ShakeDetector(Listener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * Starts listening for shakes on devices with appropriate hardware.
+ *
+ * @returns true if the device supports shake detection.
+ */
+ public boolean start(SensorManager sensorManager) {
+ // Already started?
+ if (accelerometer != null) {
+ return true;
+ }
+
+ accelerometer = sensorManager
+ .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+
+ // If this phone has an accelerometer, listen to it.
+ if (accelerometer != null) {
+ this.sensorManager = sensorManager;
+ sensorManager.registerListener(this, accelerometer,
+ SensorManager.SENSOR_DELAY_FASTEST);
+ }
+ return accelerometer != null;
+ }
+
+ /**
+ * Stops listening. Safe to call when already stopped. Ignored on devices without appropriate
+ * hardware.
+ */
+ public void stop() {
+ if (accelerometer != null) {
+ sensorManager.unregisterListener(this, accelerometer);
+ sensorManager = null;
+ accelerometer = null;
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ boolean accelerating = isAccelerating(event);
+ long timestamp = event.timestamp;
+ queue.add(timestamp, accelerating);
+ if (queue.isShaking()) {
+ /*
+ * detect time between two concecutive shakes and limit it to
+ * MIN_TIME_BETWEEN_TWO_SHAKES
+ */
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - mDetectedShakeStartTime > MIN_TIME_BETWEEN_TWO_SHAKES) {
+ queue.clear();
+ listener.hearShake();
+ mDetectedShakeStartTime = System.currentTimeMillis();
+ }
+ }
+ }
+
+ /** Returns true if the device is currently accelerating. */
+ private boolean isAccelerating(SensorEvent event) {
+ float ax = event.values[0];
+ float ay = event.values[1];
+ float az = event.values[2];
+
+ // Instead of comparing magnitude to ACCELERATION_THRESHOLD,
+ // compare their squares. This is equivalent and doesn't need the
+ // actual magnitude, which would be computed using (expesive)
+ // Math.sqrt().
+ final double magnitudeSquared = ax * ax + ay * ay + az * az;
+ return magnitudeSquared > ACCELERATION_THRESHOLD
+ * ACCELERATION_THRESHOLD;
+ }
+
+ /** Queue of samples. Keeps a running average. */
+ static class SampleQueue {
+
+ /** Window size in ns. Used to compute the average. */
+ private static final long MAX_WINDOW_SIZE = 500000000; // 0.5s
+ private static final long MIN_WINDOW_SIZE = MAX_WINDOW_SIZE >> 1; // 0.25s
+
+ /**
+ * Ensure the queue size never falls below this size, even if the device fails to deliver
+ * this many events during the time window. The LG Ally is one such device.
+ */
+ private static final int MIN_QUEUE_SIZE = 4;
+
+ private final SamplePool pool = new SamplePool();
+
+ private Sample oldest;
+ private Sample newest;
+ private int sampleCount;
+ private int acceleratingCount;
+
+ /**
+ * Adds a sample.
+ *
+ * @param timestamp in nanoseconds of sample
+ * @param accelerating true if > {@link #ACCELERATION_THRESHOLD}.
+ */
+ void add(long timestamp, boolean accelerating) {
+ // Purge samples that proceed window.
+ purge(timestamp - MAX_WINDOW_SIZE);
+
+ // Add the sample to the queue.
+ Sample added = pool.acquire();
+ added.timestamp = timestamp;
+ added.accelerating = accelerating;
+ added.next = null;
+ if (newest != null) {
+ newest.next = added;
+ }
+ newest = added;
+ if (oldest == null) {
+ oldest = added;
+ }
+
+ // Update running average.
+ sampleCount++;
+ if (accelerating) {
+ acceleratingCount++;
+ }
+ }
+
+ /** Removes all samples from this queue. */
+ void clear() {
+ while (oldest != null) {
+ Sample removed = oldest;
+ oldest = removed.next;
+ pool.release(removed);
+ }
+ newest = null;
+ sampleCount = 0;
+ acceleratingCount = 0;
+ }
+
+ /** Purges samples with timestamps older than cutoff. */
+ void purge(long cutoff) {
+ while (sampleCount >= MIN_QUEUE_SIZE && oldest != null
+ && cutoff - oldest.timestamp > 0) {
+ // Remove sample.
+ Sample removed = oldest;
+ if (removed.accelerating) {
+ acceleratingCount--;
+ }
+ sampleCount--;
+
+ oldest = removed.next;
+ if (oldest == null) {
+ newest = null;
+ }
+ pool.release(removed);
+ }
+ }
+
+ /** Copies the samples into a list, with the oldest entry at index 0. */
+ List<Sample> asList() {
+ List<Sample> list = new ArrayList<Sample>();
+ Sample s = oldest;
+ while (s != null) {
+ list.add(s);
+ s = s.next;
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if we have enough samples and more than 3/4 of those samples are
+ * accelerating.
+ */
+ boolean isShaking() {
+ return newest != null
+ && oldest != null
+ && newest.timestamp - oldest.timestamp >= MIN_WINDOW_SIZE
+ && acceleratingCount >= (sampleCount >> 1)
+ + (sampleCount >> 2);
+ }
+ }
+
+ /** An accelerometer sample. */
+ static class Sample {
+ /** Time sample was taken. */
+ long timestamp;
+
+ /** If acceleration > {@link #ACCELERATION_THRESHOLD}. */
+ boolean accelerating;
+
+ /** Next sample in the queue or pool. */
+ Sample next;
+ }
+
+ /** Pools samples. Avoids garbage collection. */
+ static class SamplePool {
+ private Sample head;
+
+ /** Acquires a sample from the pool. */
+ Sample acquire() {
+ Sample acquired = head;
+ if (acquired == null) {
+ acquired = new Sample();
+ } else {
+ // Remove instance from pool.
+ head = acquired.next;
+ }
+ return acquired;
+ }
+
+ /** Returns a sample to the pool. */
+ void release(Sample sample) {
+ sample.next = head;
+ head = sample;
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+}