diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 87713fcc..d0bb3b9e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -56,10 +56,16 @@
android:name="android.appwidget.provider"
android:resource="@xml/one_cell_widget" />
+
+
+
+
+
-
diff --git a/res/layout/one_cell_widget_configure.xml b/res/layout/one_cell_widget_configure.xml
new file mode 100644
index 00000000..c848e32f
--- /dev/null
+++ b/res/layout/one_cell_widget_configure.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 04ac1c06..8f5a40b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,8 @@
Starting up...
+ Place Widget
+ Double tap widget to open player (reduces responsiveness)Choose Playlist Name
diff --git a/res/xml/one_cell_widget.xml b/res/xml/one_cell_widget.xml
index e00bd2f7..fee95d38 100644
--- a/res/xml/one_cell_widget.xml
+++ b/res/xml/one_cell_widget.xml
@@ -20,5 +20,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="72dip"
android:minHeight="72dip"
+ android:configure="org.kreed.vanilla.OneCellWidgetConfigure"
android:initialLayout="@layout/one_cell_widget"
/>
\ No newline at end of file
diff --git a/src/org/kreed/vanilla/FullPlaybackActivity.java b/src/org/kreed/vanilla/FullPlaybackActivity.java
index a22fcdf3..1f4b2a62 100644
--- a/src/org/kreed/vanilla/FullPlaybackActivity.java
+++ b/src/org/kreed/vanilla/FullPlaybackActivity.java
@@ -42,7 +42,7 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
* A Handler running on the UI thread, in contrast with mHandler which runs
* on a worker thread.
*/
- private Handler mUiHandler;
+ private Handler mUiHandler = new Handler(this);
private RelativeLayout mMessageOverlay;
private View mControlsTop;
@@ -62,8 +62,6 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
setContentView(R.layout.full_playback);
- mUiHandler = new Handler(this);
-
mCoverView = (CoverView)findViewById(R.id.cover_view);
mCoverView.mHandler = mHandler;
mCoverView.setOnClickListener(this);
diff --git a/src/org/kreed/vanilla/OneCellWidget.java b/src/org/kreed/vanilla/OneCellWidget.java
index 509e1b23..4dce4353 100644
--- a/src/org/kreed/vanilla/OneCellWidget.java
+++ b/src/org/kreed/vanilla/OneCellWidget.java
@@ -24,6 +24,8 @@ import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.util.TypedValue;
import android.widget.RemoteViews;
@@ -35,14 +37,25 @@ public class OneCellWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager manager, int[] ids)
{
- SongTimeline timeline = new SongTimeline();
- timeline.loadState(context);
- RemoteViews views = createViews(context, timeline.getSong(0), 0);
- manager.updateAppWidget(ids, views);
+ Song song;
+ int state;
- // If we generated a new current song (because the PlaybackService has
- // never been started), then we need to save the state.
- timeline.saveState(context, 0);
+ if (ContextApplication.hasService()) {
+ PlaybackService service = ContextApplication.getService();
+ song = service.getSong(0);
+ state = service.getState();
+ } else {
+ SongTimeline timeline = new SongTimeline();
+ timeline.loadState(context);
+ song = timeline.getSong(0);
+ state = 0;
+
+ // If we generated a new current song (because the PlaybackService has
+ // never been started), then we need to save the state.
+ timeline.saveState(context, 0);
+ }
+
+ updateWidget(context, manager, ids, song, state);
}
/**
@@ -59,45 +72,51 @@ public class OneCellWidget extends AppWidgetProvider {
Song song = intent.getParcelableExtra("song");
int state = intent.getIntExtra("state", -1);
- ComponentName widget = new ComponentName(context, OneCellWidget.class);
- RemoteViews views = createViews(context, song, state);
-
- AppWidgetManager.getInstance(context).updateAppWidget(widget, views);
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ int[] ids = manager.getAppWidgetIds(new ComponentName(context, OneCellWidget.class));
+ updateWidget(context, manager, ids, song, state);
}
}
/**
- * Create the RemoteViews that will be used to update the widget.
+ * Populate the widgets with the given ids with the given info.
*
* @param context A Context to use.
+ * @param manager The AppWidgetManager that will be used to update the
+ * widget.
+ * @param ids An array containing the ids of all the widgets to update.
* @param song The current Song in PlaybackService.
* @param state The current PlaybackService state.
- * @return A RemoteViews instance, ready to be sent with updateAppWidget.
*/
- public static RemoteViews createViews(Context context, Song song, int state)
+ public static void updateWidget(Context context, AppWidgetManager manager, int[] ids, Song song, int state)
{
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.one_cell_widget);
+ for (int i = 0; i != ids.length; ++i) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean doubleTap = settings.getBoolean("double_tap_" + ids[i], false);
- if (state != -1) {
- boolean playing = (state & PlaybackService.FLAG_PLAYING) != 0;
- views.setImageViewResource(R.id.play_pause, playing ? R.drawable.hidden_pause : R.drawable.hidden_play);
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.one_cell_widget);
+
+ if (state != -1) {
+ boolean playing = (state & PlaybackService.FLAG_PLAYING) != 0;
+ views.setImageViewResource(R.id.play_pause, playing ? R.drawable.hidden_pause : R.drawable.hidden_play);
+ }
+
+ Intent playPause = new Intent(context, PlaybackService.class);
+ playPause.setAction(doubleTap ? PlaybackService.ACTION_TOGGLE_PLAYBACK_DELAYED : PlaybackService.ACTION_TOGGLE_PLAYBACK);
+ views.setOnClickPendingIntent(R.id.play_pause, PendingIntent.getService(context, 0, playPause, 0));
+
+ Intent next = new Intent(context, PlaybackService.class);
+ next.setAction(doubleTap ? PlaybackService.ACTION_NEXT_SONG_DELAYED : PlaybackService.ACTION_NEXT_SONG);
+ views.setOnClickPendingIntent(R.id.next, PendingIntent.getService(context, 0, next, 0));
+
+ if (song == null) {
+ views.setImageViewResource(R.id.cover_view, R.drawable.icon);
+ } else {
+ int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 72, context.getResources().getDisplayMetrics());
+ views.setImageViewBitmap(R.id.cover_view, CoverBitmap.createCompactBitmap(song, size, size));
+ }
+
+ manager.updateAppWidget(ids[i], views);
}
-
- Intent playPause = new Intent(context, PlaybackService.class);
- playPause.setAction(PlaybackService.ACTION_TOGGLE_PLAYBACK);
- views.setOnClickPendingIntent(R.id.play_pause, PendingIntent.getService(context, 0, playPause, 0));
-
- Intent next = new Intent(context, PlaybackService.class);
- next.setAction(PlaybackService.ACTION_NEXT_SONG);
- views.setOnClickPendingIntent(R.id.next, PendingIntent.getService(context, 0, next, 0));
-
- if (song == null) {
- views.setImageViewResource(R.id.cover_view, R.drawable.icon);
- } else {
- int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 72, context.getResources().getDisplayMetrics());
- views.setImageViewBitmap(R.id.cover_view, CoverBitmap.createCompactBitmap(song, size, size));
- }
-
- return views;
}
}
diff --git a/src/org/kreed/vanilla/OneCellWidgetConfigure.java b/src/org/kreed/vanilla/OneCellWidgetConfigure.java
new file mode 100644
index 00000000..45b052c5
--- /dev/null
+++ b/src/org/kreed/vanilla/OneCellWidgetConfigure.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Christopher Eby
+ *
+ * This file is part of Vanilla Music Player.
+ *
+ * Vanilla Music Player is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Vanilla Music Player is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.kreed.vanilla;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+
+/**
+ * A configuration dialog for the OneCellWidget. Displays the double tap
+ * preference.
+ */
+public class OneCellWidgetConfigure extends Activity implements OnClickListener {
+ /**
+ * The id of the widget we are configuring.
+ */
+ private int mAppWidgetId;
+ /**
+ * The check box for the double-tap-opens-players preference.
+ */
+ private CheckBox mDoubleTap;
+
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ setResult(RESULT_CANCELED);
+
+ setContentView(R.layout.one_cell_widget_configure);
+
+ mDoubleTap = (CheckBox)findViewById(R.id.double_tap);
+ findViewById(R.id.place).setOnClickListener(this);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null)
+ mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+
+ if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
+ finish();
+ }
+
+ public void onClick(View view)
+ {
+ boolean doubleTap = mDoubleTap.isChecked();
+
+ // save the setting
+ SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
+ prefs.putBoolean("double_tap_" + mAppWidgetId, doubleTap);
+ prefs.commit();
+
+ AppWidgetManager manager = AppWidgetManager.getInstance(this);
+ new OneCellWidget().onUpdate(this, manager, new int[] { mAppWidgetId });
+
+ Intent resultValue = new Intent();
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/src/org/kreed/vanilla/PlaybackService.java b/src/org/kreed/vanilla/PlaybackService.java
index 0bbdaf40..b8e84637 100644
--- a/src/org/kreed/vanilla/PlaybackService.java
+++ b/src/org/kreed/vanilla/PlaybackService.java
@@ -53,8 +53,35 @@ import android.widget.Toast;
public final class PlaybackService extends Service implements Handler.Callback, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, SharedPreferences.OnSharedPreferenceChangeListener, SongTimeline.Callback {
private static final int NOTIFICATION_ID = 2;
+ /**
+ * Action for startService: toggle playback on/off.
+ */
public static final String ACTION_TOGGLE_PLAYBACK = "org.kreed.vanilla.action.TOGGLE_PLAYBACK";
+ /**
+ * Action for startService: toggle playback on/off.
+ *
+ * Unlike {@link PlaybackService#ACTION_TOGGLE_PLAYBACK}, the toggle does
+ * not occur immediately. Instead, it is delayed so that if two of these
+ * actions are received within 400 ms, the playback activity is opened
+ * instead.
+ */
+ public static final String ACTION_TOGGLE_PLAYBACK_DELAYED = "org.kreed.vanilla.action.TOGGLE_PLAYBACK_DELAYED";
+ /**
+ * Action for startService: advance to the next song.
+ */
public static final String ACTION_NEXT_SONG = "org.kreed.vanilla.action.NEXT_SONG";
+ /**
+ * Action for startService: advance to the next song.
+ *
+ * Unlike {@link PlaybackService#ACTION_NEXT_SONG}, the toggle does
+ * not occur immediately. Instead, it is delayed so that if two of these
+ * actions are received within 400 ms, the playback activity is opened
+ * instead.
+ */
+ public static final String ACTION_NEXT_SONG_DELAYED = "org.kreed.vanilla.action.NEXT_SONG_DELAYED";
+ /**
+ * Action for startService: go back to the previous song.
+ */
public static final String ACTION_PREVIOUS_SONG = "org.kreed.vanilla.action.PREVIOUS_SONG";
/**
* Intent action that may be invoked through startService.
@@ -194,11 +221,25 @@ public final class PlaybackService extends Service implements Handler.Callback,
if (ACTION_TOGGLE_PLAYBACK.equals(action)) {
go(0, false);
+ } else if (ACTION_TOGGLE_PLAYBACK_DELAYED.equals(action)) {
+ if (mHandler.hasMessages(CALL_GO, Integer.valueOf(0))) {
+ mHandler.removeMessages(CALL_GO, Integer.valueOf(0));
+ startActivity(new Intent(this, FullPlaybackActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(CALL_GO, Integer.valueOf(0)), 400);
+ }
} else if (ACTION_NEXT_SONG.equals(action)) {
// Preemptively broadcast an update in attempt to hasten UI
// feedback.
broadcastReplaceSong(0, getSong(+1));
go(1, false);
+ } else if (ACTION_NEXT_SONG_DELAYED.equals(action)) {
+ if (mHandler.hasMessages(CALL_GO, Integer.valueOf(1))) {
+ mHandler.removeMessages(CALL_GO, Integer.valueOf(1));
+ startActivity(new Intent(this, FullPlaybackActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(CALL_GO, Integer.valueOf(1)), 400);
+ }
} else if (ACTION_PREVIOUS_SONG.equals(action)) {
go(-1, false);
} else if (ACTION_PLAY_ITEMS.equals(action)) {
@@ -279,6 +320,17 @@ public final class PlaybackService extends Service implements Handler.Callback,
mNotificationManager.cancel(NOTIFICATION_ID);
}
+ /**
+ * Return the SharedPreferences instance containing the PlaybackService
+ * settings, creating it if necessary.
+ */
+ private SharedPreferences getSettings()
+ {
+ if (mSettings == null)
+ mSettings = PreferenceManager.getDefaultSharedPreferences(this);
+ return mSettings;
+ }
+
private void initialize()
{
ContextApplication.broadcast(new Intent(EVENT_INITIALIZED));
@@ -307,18 +359,17 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
}
- if (mSettings == null)
- mSettings = PreferenceManager.getDefaultSharedPreferences(this);
- mSettings.registerOnSharedPreferenceChangeListener(this);
- mHeadsetOnly = mSettings.getBoolean("headset_only", false);
- mNotificationMode = Integer.parseInt(mSettings.getString("notification_mode", "1"));
- mScrobble = mSettings.getBoolean("scrobble", false);
- float volume = mSettings.getFloat("volume", 1.0f);
+ SharedPreferences settings = getSettings();
+ settings.registerOnSharedPreferenceChangeListener(this);
+ mHeadsetOnly = settings.getBoolean("headset_only", false);
+ mNotificationMode = Integer.parseInt(settings.getString("notification_mode", "1"));
+ mScrobble = settings.getBoolean("scrobble", false);
+ float volume = settings.getFloat("volume", 1.0f);
if (volume != 1.0f) {
mCurrentVolume = mUserVolume = volume;
mMediaPlayer.setVolume(volume, volume);
}
- mIdleTimeout = mSettings.getBoolean("use_idle_timeout", false) ? mSettings.getInt("idle_timeout", 3600) : 0;
+ mIdleTimeout = settings.getBoolean("use_idle_timeout", false) ? settings.getInt("idle_timeout", 3600) : 0;
PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "VanillaMusicSongChangeLock");
@@ -335,22 +386,23 @@ public final class PlaybackService extends Service implements Handler.Callback,
private void loadPreference(String key)
{
+ SharedPreferences settings = getSettings();
if ("headset_pause".equals(key)) {
- mHeadsetPause = mSettings.getBoolean("headset_pause", true);
+ mHeadsetPause = settings.getBoolean("headset_pause", true);
} else if ("headset_only".equals(key)) {
- mHeadsetOnly = mSettings.getBoolean(key, false);
+ mHeadsetOnly = settings.getBoolean(key, false);
if (mHeadsetOnly && isSpeakerOn())
unsetFlag(FLAG_PLAYING);
} else if ("remote_player".equals(key)) {
// the preference is loaded in SongNotification class
updateNotification(getSong(0));
} else if ("notification_mode".equals(key)){
- mNotificationMode = Integer.parseInt(mSettings.getString("notification_mode", "1"));
+ mNotificationMode = Integer.parseInt(settings.getString("notification_mode", "1"));
updateNotification(getSong(0));
} else if ("scrobble".equals(key)) {
- mScrobble = mSettings.getBoolean("scrobble", false);
+ mScrobble = settings.getBoolean("scrobble", false);
} else if ("volume".equals(key)) {
- float volume = mSettings.getFloat("volume", 1.0f);
+ float volume = settings.getFloat("volume", 1.0f);
mCurrentVolume = mUserVolume = volume;
if (mMediaPlayer != null) {
synchronized (mMediaPlayer) {
@@ -358,10 +410,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
}
} else if ("media_button".equals(key)) {
- MediaButtonHandler.getInstance().setUseHeadsetControls(mSettings.getBoolean("media_button", true));
+ MediaButtonHandler.getInstance().setUseHeadsetControls(settings.getBoolean("media_button", true));
setupReceiver();
} else if ("use_idle_timeout".equals(key) || "idle_timeout".equals(key)) {
- mIdleTimeout = mSettings.getBoolean("use_idle_timeout", false) ? mSettings.getInt("idle_timeout", 3600) : 0;
+ mIdleTimeout = settings.getBoolean("use_idle_timeout", false) ? settings.getInt("idle_timeout", 3600) : 0;
userActionTriggered();
}
}
@@ -702,6 +754,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
* arg1 should be the progress in the fade as a percentage, 1-100.
*/
private static final int FADE_OUT = 7;
+ /**
+ * Calls {@link PlaybackService#go(int, boolean)} with the given delta.
+ *
+ * obj should an Integer representing the delta to pass to go.
+ */
+ private static final int CALL_GO = 8;
private static final int SAVE_STATE = 12;
private static final int PROCESS_SONG = 13;
@@ -719,6 +777,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
if (mWakeLock != null && mWakeLock.isHeld())
mWakeLock.release();
break;
+ case CALL_GO:
+ int delta = (Integer)message.obj;
+ go(delta, false);
+ break;
case GO:
if (message.arg1 == 0)
toggleFlag(FLAG_PLAYING);