Add an option to bring back the double-tap-widget-opens-activity behavior

This commit is contained in:
Christopher Eby 2010-05-25 23:21:27 -05:00
parent aa07a5264c
commit cf34354bba
8 changed files with 264 additions and 54 deletions

View File

@ -56,10 +56,16 @@
android:name="android.appwidget.provider"
android:resource="@xml/one_cell_widget" />
</receiver>
<activity
android:name=".OneCellWidgetConfigure"
android:theme="@android:style/Theme.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<receiver android:name="MediaButtonReceiver" >
<intent-filter android:priority="1000">
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="org.kreed.vanilla.action.ENABLE_RECEIVER" />
</intent-filter>
</receiver>
<service android:name="PlaybackService" />

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/double_tap"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/double_tap_widget" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="6dip"
android:background="#fff" >
<Button android:id="@+id/place"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/place_widget"
android:singleLine="true" />
</FrameLayout>
</LinearLayout>

View File

@ -36,6 +36,8 @@
<!-- Widgets -->
<string name="starting">Starting up...</string>
<string name="place_widget">Place Widget</string>
<string name="double_tap_widget">Double tap widget to open player (reduces responsiveness)</string>
<!-- New Playlist Dialog -->
<string name="choose_playlist_name">Choose Playlist Name</string>

View File

@ -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"
/>

View File

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

View File

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

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

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