Don't use binders or broadcasts to communicate within the application

This commit is contained in:
Christopher Eby 2010-04-24 10:49:31 -05:00
parent 2ca9c39f1c
commit 69ea872f75
9 changed files with 211 additions and 303 deletions

View File

@ -26,11 +26,19 @@ import android.app.Application;
import android.content.Context;
import android.content.Intent;
/**
* Subclass of Application that provides various static utility functions
*/
public class ContextApplication extends Application {
private static ContextApplication mInstance;
private static ArrayList<Activity> mActivities;
private static Random mRandom;
/**
* The PlaybackService instance, if any.
*/
public static PlaybackService service;
public ContextApplication()
{
mInstance = this;
@ -61,12 +69,31 @@ public class ContextApplication extends Application {
mActivities.remove(activity);
}
public static void quit(Context context)
/**
* Send a broadcast to all PlaybackActivities that have been added with
* addActivity.
*
* @param intent The intent to be sent as a broadcast
*/
public static void broadcast(Intent intent)
{
ArrayList<Activity> list = mActivities;
if (list == null)
return;
for (int i = list.size(); --i != -1; ) {
Activity activity = list.get(i);
if (activity instanceof PlaybackActivity)
((PlaybackActivity)activity).receive(intent);
}
}
public static void quit()
{
context.stopService(new Intent(context, PlaybackService.class));
if (mActivities != null) {
for (int i = mActivities.size(); --i != -1; )
mActivities.remove(i).finish();
}
mInstance.stopService(new Intent(mInstance, PlaybackService.class));
}
}

View File

@ -18,8 +18,6 @@
package org.kreed.vanilla;
import org.kreed.vanilla.IPlaybackService;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@ -32,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@ -43,10 +40,6 @@ import android.view.ViewConfiguration;
import android.widget.Scroller;
public final class CoverView extends View implements Handler.Callback {
public static interface Callback {
void songChanged(Song song);
};
private static final int STORE_SIZE = 3;
private static int SNAP_VELOCITY = -1;
@ -59,10 +52,8 @@ public final class CoverView extends View implements Handler.Callback {
private static Bitmap ALBUM_ICON;
private static Bitmap ARTIST_ICON;
private IPlaybackService mService;
private Scroller mScroller;
private Handler mHandler = new Handler(this);
private Callback mCallback;
private Song[] mSongs = new Song[3];
private Bitmap[] mBitmaps = new Bitmap[3];
@ -124,28 +115,17 @@ public final class CoverView extends View implements Handler.Callback {
return mSeparateInfo;
}
public void setCallback(Callback callback)
{
mCallback = callback;
}
public void setSeparateInfo(boolean separate)
{
mSeparateInfo = separate;
}
public void setPlaybackService(IPlaybackService service)
/**
* Query the service for initial song info.
*/
public void initialize()
{
mService = service;
if (mService != null) {
try {
mTimelinePos = mService.getTimelinePos();
} catch (RemoteException e) {
mService = null;
}
}
mTimelinePos = ContextApplication.service.getTimelinePos();
refreshSongs();
}
@ -410,9 +390,6 @@ public final class CoverView extends View implements Handler.Callback {
private void shiftCover(int delta)
{
if (mService == null)
return;
int i = delta > 0 ? STORE_SIZE - 1 : 0;
if (mSongs[i] == null)
@ -429,16 +406,11 @@ public final class CoverView extends View implements Handler.Callback {
reset();
invalidate();
if (mCallback != null)
mCallback.songChanged(mSongs[STORE_SIZE / 2]);
mHandler.sendEmptyMessage(i);
}
public void go(int delta) throws RemoteException
public void go(int delta)
{
if (mService == null)
throw new RemoteException();
mHandler.sendMessage(mHandler.obtainMessage(GO, delta, 0));
}
@ -582,30 +554,32 @@ public final class CoverView extends View implements Handler.Callback {
public boolean handleMessage(Message message)
{
try {
switch (message.what) {
case GO:
shiftCover(message.arg1);
break;
default:
int i = message.what;
if (message.obj == null)
mSongs[i] = mService.getSong(i - STORE_SIZE / 2);
else
mSongs[i] = (Song)message.obj;
createBitmap(i);
if (i == STORE_SIZE / 2)
reset();
break;
}
} catch (RemoteException e) {
mService = null;
switch (message.what) {
case GO:
shiftCover(message.arg1);
break;
default:
int i = message.what;
if (message.obj == null)
mSongs[i] = ContextApplication.service.getSong(i - STORE_SIZE / 2);
else
mSongs[i] = (Song)message.obj;
createBitmap(i);
if (i == STORE_SIZE / 2)
reset();
break;
}
return true;
}
public void onReceive(Intent intent)
/**
* Handle an intent broadcasted by the PlaybackService. This must be called
* to react to song changes in PlaybackService.
*
* @param intent The intent that was broadcast
*/
public void receive(Intent intent)
{
String action = intent.getAction();
if (PlaybackService.EVENT_REPLACE_SONG.equals(action)) {

View File

@ -18,7 +18,6 @@
package org.kreed.vanilla;
import org.kreed.vanilla.IPlaybackService;
import org.kreed.vanilla.R;
import android.content.Intent;
@ -26,7 +25,6 @@ import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.view.KeyEvent;
import android.view.Menu;
@ -39,11 +37,10 @@ import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class FullPlaybackActivity extends PlaybackActivity implements View.OnClickListener, SeekBar.OnSeekBarChangeListener, CoverView.Callback {
public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.OnSeekBarChangeListener {
private RelativeLayout mMessageOverlay;
private View mControlsTop;
private View mControlsBottom;
private SongSelector mSongSelector;
private ImageView mPlayPauseButton;
private SeekBar mSeekBar;
@ -141,34 +138,22 @@ public class FullPlaybackActivity extends PlaybackActivity implements View.OnCli
}
@Override
protected void setService(IPlaybackService service)
protected void onServiceReady()
{
super.setService(service);
super.onServiceReady();
if (service != null) {
try {
mDuration = service.getDuration();
} catch (RemoteException e) {
}
}
mDuration = ContextApplication.service.getDuration();
}
@Override
protected void onServiceChange(Intent intent)
public void receive(Intent intent)
{
super.onServiceChange(intent);
super.receive(intent);
if (mService != null) {
try {
mDuration = mService.getDuration();
mHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);
} catch (RemoteException e) {
setService(null);
}
if (PlaybackService.EVENT_CHANGED.equals(intent.getAction())) {
mDuration = ContextApplication.service.getDuration();
mHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);
}
if (mSongSelector != null)
mSongSelector.onServiceChange(intent);
}
@Override
@ -241,13 +226,7 @@ public class FullPlaybackActivity extends PlaybackActivity implements View.OnCli
if (mPaused || mControlsTop.getVisibility() != View.VISIBLE || (mState & PlaybackService.FLAG_PLAYING) == 0)
return;
int position = 0;
if (mService != null) {
try {
position = mService.getPosition();
} catch (RemoteException e) {
}
}
int position = ContextApplication.service.getPosition();
if (!mSeekBarTracking)
mSeekBar.setProgress(mDuration == 0 ? 0 : (int)(1000 * position / mDuration));
@ -302,13 +281,8 @@ public class FullPlaybackActivity extends PlaybackActivity implements View.OnCli
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
if (!fromUser || mService == null)
return;
try {
mService.seekToProgress(progress);
} catch (RemoteException e) {
}
if (fromUser)
ContextApplication.service.seekToProgress(progress);
}
public void onStartTrackingTouch(SeekBar seekBar)
@ -320,10 +294,4 @@ public class FullPlaybackActivity extends PlaybackActivity implements View.OnCli
{
mSeekBarTracking = false;
}
public void songChanged(Song song)
{
if (mSongSelector != null)
mSongSelector.updateSong(song);
}
}

View File

@ -1,33 +0,0 @@
/*
* 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 org.kreed.vanilla.Song;
interface IPlaybackService {
Song getSong(int delta);
int getState();
int getPosition();
int getDuration();
int getTimelinePos();
void setCurrentSong(int delta);
void toggleFlag(int flag);
void seekToProgress(int progress);
}

View File

@ -72,7 +72,7 @@ public class MiniPlaybackActivity extends PlaybackActivity implements View.OnCli
{
switch (view.getId()) {
case R.id.kill_button:
ContextApplication.quit(this);
ContextApplication.quit();
break;
case R.id.open_button:
startActivity(new Intent(this, FullPlaybackActivity.class));

View File

@ -19,29 +19,20 @@
package org.kreed.vanilla;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class PlaybackActivity extends Activity implements ServiceConnection, Handler.Callback {
public class PlaybackActivity extends Activity implements Handler.Callback, View.OnClickListener {
Handler mHandler = new Handler(this);
CoverView mCoverView;
IPlaybackService mService;
int mState;
@Override
@ -63,38 +54,14 @@ public class PlaybackActivity extends Activity implements ServiceConnection, Han
{
super.onStart();
Intent intent = new Intent(this, PlaybackService.class);
startService(intent);
bindService(intent, this, Context.BIND_AUTO_CREATE);
IntentFilter filter = new IntentFilter();
filter.addAction(PlaybackService.EVENT_CHANGED);
filter.addAction(PlaybackService.EVENT_REPLACE_SONG);
registerReceiver(mReceiver, filter);
startService(new Intent(this, PlaybackService.class));
}
@Override
public void onStop()
{
super.onStop();
try {
unbindService(this);
} catch (IllegalArgumentException e) {
// we have not registered the service yet
}
try {
unregisterReceiver(mReceiver);
} catch (IllegalArgumentException e) {
// we haven't registered the receiver yet
}
}
public static boolean handleKeyLongPress(Context context, int keyCode)
public static boolean handleKeyLongPress(int keyCode)
{
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
ContextApplication.quit(context);
ContextApplication.quit();
return true;
}
@ -104,30 +71,25 @@ public class PlaybackActivity extends Activity implements ServiceConnection, Han
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event)
{
return handleKeyLongPress(this, keyCode);
return handleKeyLongPress(keyCode);
}
public void onClick(View view)
{
try {
switch (view.getId()) {
case R.id.next:
if (mCoverView != null)
mCoverView.go(1);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, 1, 0));
break;
case R.id.play_pause:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_PLAYING, 0));
break;
case R.id.previous:
if (mCoverView != null)
mCoverView.go(-1);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, -1, 0));
break;
}
} catch (RemoteException e) {
Log.e("VanillaMusic", "service dead", e);
setService(null);
switch (view.getId()) {
case R.id.next:
if (mCoverView != null)
mCoverView.go(1);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, 1, 0));
break;
case R.id.play_pause:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_PLAYING, 0));
break;
case R.id.previous:
if (mCoverView != null)
mCoverView.go(-1);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, -1, 0));
break;
}
}
@ -143,52 +105,34 @@ public class PlaybackActivity extends Activity implements ServiceConnection, Han
}
/**
* Sets up components when the PlaybackService is bound to. Override to
* implement further post-connection behavior.
*
* @param service PlaybackService interface
* Sets up components when the PlaybackService is initialized and available to
* interact with. Override to implement further post-initialization behavior.
*/
protected void setService(IPlaybackService service)
protected void onServiceReady()
{
mService = service;
if (mCoverView != null)
mCoverView.setPlaybackService(service);
mCoverView.initialize();
if (service != null) {
try {
setState(service.getState());
} catch (RemoteException e) {
}
}
setState(ContextApplication.service.getState());
}
public void onServiceConnected(ComponentName name, IBinder service)
/**
* Called by PlaybackService when it broadcasts an intent.
*
* @param intent The intent that was broadcast.
*/
public void receive(Intent intent)
{
setService(IPlaybackService.Stub.asInterface(service));
}
String action = intent.getAction();
public void onServiceDisconnected(ComponentName name)
{
setService(null);
if (PlaybackService.EVENT_INITIALIZED.equals(action))
onServiceReady();
if (mCoverView != null)
mCoverView.receive(intent);
if (PlaybackService.EVENT_CHANGED.equals(action))
setState(intent.getIntExtra("state", 0));
}
protected void onServiceChange(Intent intent)
{
setState(intent.getIntExtra("state", 0));
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent)
{
if (mCoverView != null)
mCoverView.onReceive(intent);
if (PlaybackService.EVENT_CHANGED.equals(intent.getAction()))
onServiceChange(intent);
}
};
static final int MENU_QUIT = 0;
static final int MENU_DISPLAY = 1;
static final int MENU_PREFS = 2;
@ -223,7 +167,7 @@ public class PlaybackActivity extends Activity implements ServiceConnection, Han
{
switch (item.getItemId()) {
case MENU_QUIT:
ContextApplication.quit(this);
ContextApplication.quit();
return true;
case MENU_SHUFFLE:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_SHUFFLE, 0));
@ -264,18 +208,10 @@ public class PlaybackActivity extends Activity implements ServiceConnection, Han
if (text != -1)
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
try {
mService.toggleFlag(flag);
} catch (RemoteException e) {
setService(null);
}
ContextApplication.service.toggleFlag(flag);
break;
case MSG_SET_SONG:
try {
mService.setCurrentSong(message.arg1);
} catch (RemoteException e) {
setService(null);
}
ContextApplication.service.setCurrentSong(message.arg1);
break;
default:
return false;

View File

@ -25,8 +25,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import org.kreed.vanilla.IPlaybackService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@ -66,6 +64,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
public static final String EVENT_REPLACE_SONG = "org.kreed.vanilla.event.REPLACE_SONG";
public static final String EVENT_CHANGED = "org.kreed.vanilla.event.CHANGED";
public static final String EVENT_INITIALIZED = "org.kreed.vanilla.event.INITIALIZED";
public static final int ACTION_PLAY = 0;
public static final int ACTION_ENQUEUE = 1;
@ -121,6 +120,8 @@ public final class PlaybackService extends Service implements Handler.Callback,
@Override
public void onCreate()
{
ContextApplication.service = this;
HandlerThread thread = new HandlerThread("PlaybackService");
thread.start();
@ -190,6 +191,8 @@ public final class PlaybackService extends Service implements Handler.Callback,
@Override
public void onDestroy()
{
ContextApplication.service = null;
super.onDestroy();
if (mSongTimeline != null)
@ -253,6 +256,8 @@ public final class PlaybackService extends Service implements Handler.Callback,
private void initialize()
{
sendBroadcast(new Intent(EVENT_INITIALIZED));
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
@ -329,6 +334,13 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
}
@Override
public void sendBroadcast(Intent intent)
{
ContextApplication.broadcast(intent);
super.sendBroadcast(intent);
}
private void broadcastReplaceSong(int delta, Song song)
{
Intent intent = new Intent(EVENT_REPLACE_SONG);
@ -466,7 +478,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
return (mAudioManager.getRouting(mAudioManager.getMode()) & AudioManager.ROUTE_SPEAKER) != 0;
}
void toggleFlag(int flag)
/**
* Toggle a flag in the state on or off
*
* @param flag The flag to be toggled (FLAG_PLAYING, FLAG_SHUFFLE, or FLAG_REPEAT)
*/
public void toggleFlag(int flag)
{
synchronized (mStateLock) {
if ((mState & flag) == 0)
@ -487,7 +504,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
return songs;
}
void setCurrentSong(int delta)
/**
* Move <code>delta</code> places away from the current song.
*/
public void setCurrentSong(int delta)
{
if (mMediaPlayer == null)
return;
@ -558,7 +578,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
return true;
}
Song getSong(int delta)
/**
* Returns the song <code>delta</code> places away from the current position.
*/
public Song getSong(int delta)
{
if (mSongTimeline == null)
return null;
@ -895,66 +918,67 @@ public final class PlaybackService extends Service implements Handler.Callback,
return true;
}
public IPlaybackService.Stub mBinder = new IPlaybackService.Stub() {
public Song getSong(int delta)
{
return PlaybackService.this.getSong(delta);
/**
* Returns the current service state. The state comprises several individual
* flags.
*/
public int getState()
{
synchronized (mStateLock) {
return mState;
}
}
public int getState()
{
synchronized (mStateLock) {
return mState;
}
/**
* Returns the current position in current song in milliseconds.
*/
public int getPosition()
{
if (mMediaPlayer == null)
return 0;
synchronized (mMediaPlayer) {
return mMediaPlayer.getCurrentPosition();
}
}
public int getPosition()
{
if (mMediaPlayer == null)
return 0;
synchronized (mMediaPlayer) {
return mMediaPlayer.getCurrentPosition();
}
/**
* Returns the duration of the current song in milliseconds.
*/
public int getDuration()
{
if (mMediaPlayer == null)
return 0;
synchronized (mMediaPlayer) {
return mMediaPlayer.getDuration();
}
}
public int getDuration()
{
if (mMediaPlayer == null)
return 0;
synchronized (mMediaPlayer) {
return mMediaPlayer.getDuration();
}
}
/**
* Returns the position of the current song in the song timeline.
*/
public int getTimelinePos()
{
return mTimelinePos;
}
public int getTimelinePos()
{
return mTimelinePos;
/**
* Seek to a position in the current song.
*
* @param progress Proportion of song completed (where 1000 is the end of the song)
*/
public void seekToProgress(int progress)
{
if (mMediaPlayer == null)
return;
synchronized (mMediaPlayer) {
long position = (long)mMediaPlayer.getDuration() * progress / 1000;
mMediaPlayer.seekTo((int)position);
}
public void setCurrentSong(int delta)
{
PlaybackService.this.setCurrentSong(delta);
}
public void toggleFlag(int flag)
{
PlaybackService.this.toggleFlag(flag);
}
public void seekToProgress(int progress)
{
if (mMediaPlayer == null)
return;
synchronized (mMediaPlayer) {
long position = (long)mMediaPlayer.getDuration() * progress / 1000;
mMediaPlayer.seekTo((int)position);
}
}
};
}
@Override
public IBinder onBind(Intent intent)
public IBinder onBind(Intent intents)
{
return mBinder;
return null;
}
}
}

View File

@ -43,6 +43,6 @@ public class PreferencesActivity extends PreferenceActivity {
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event)
{
return PlaybackActivity.handleKeyLongPress(this, keyCode);
return PlaybackActivity.handleKeyLongPress(keyCode);
}
}

View File

@ -51,7 +51,7 @@ import android.widget.TabHost;
import android.widget.TextView;
import android.widget.Toast;
public class SongSelector extends PlaybackActivity implements AdapterView.OnItemClickListener, TextWatcher, View.OnClickListener, TabHost.OnTabChangeListener, Filter.FilterListener {
public class SongSelector extends PlaybackActivity implements AdapterView.OnItemClickListener, TextWatcher, TabHost.OnTabChangeListener, Filter.FilterListener {
private TabHost mTabHost;
private View mSearchBox;
@ -142,6 +142,15 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mLastActedId = 0;
}
@Override
protected void onServiceReady()
{
super.onServiceReady();
if (mStatusText != null)
onSongChange(ContextApplication.service.getSong(0));
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
@ -367,12 +376,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
getAdapter(i).filter(text, null);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event)
{
return PlaybackActivity.handleKeyLongPress(this, keyCode);
}
private static final int MSG_INIT = 10;
@Override
@ -415,12 +418,21 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mSearchBox.requestFocus();
}
/**
* Call to update the status text for a newly-playing song.
*/
private void onSongChange(Song song)
{
if (mStatusText != null)
mStatusText.setText(song == null ? getResources().getText(R.string.none) : song.title);
}
@Override
protected void onServiceChange(Intent intent)
public void receive(Intent intent)
{
if (mStatusText != null) {
Song song = intent.getParcelableExtra("song");
mStatusText.setText(song == null ? getResources().getText(R.string.none) : song.title);
onSongChange(song);
}
}