/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see .
Copyright 2010 (C) Sindre Mehus
*/
package github.daneren2005.subphonic.provider;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RemoteViews;
import github.daneren2005.subphonic.R;
import github.daneren2005.subphonic.activity.DownloadActivity;
import github.daneren2005.subphonic.activity.MainActivity;
import github.daneren2005.subphonic.domain.MusicDirectory;
import github.daneren2005.subphonic.service.DownloadService;
import github.daneren2005.subphonic.service.DownloadServiceImpl;
import github.daneren2005.subphonic.util.FileUtil;
/**
* Simple widget to show currently playing album art along
* with play/pause and next track buttons.
*
* Based on source code from the stock Android Music app.
*
* @author Sindre Mehus
*/
public class SubsonicAppWidgetProvider1 extends AppWidgetProvider {
private static SubsonicAppWidgetProvider1 instance;
private static final String TAG = SubsonicAppWidgetProvider1.class.getSimpleName();
public static synchronized SubsonicAppWidgetProvider1 getInstance() {
if (instance == null) {
instance = new SubsonicAppWidgetProvider1();
}
return instance;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
defaultAppWidget(context, appWidgetIds);
}
/**
* Initialize given widgets to default state, where we launch Subsonic on default click
* and hide actions if service not running.
*/
private void defaultAppWidget(Context context, int[] appWidgetIds) {
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
linkButtons(context, views, false);
pushUpdate(context, appWidgetIds, views);
}
private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {
// Update specific list of appWidgetIds if given, otherwise default to all
final AppWidgetManager manager = AppWidgetManager.getInstance(context);
if (appWidgetIds != null) {
manager.updateAppWidget(appWidgetIds, views);
} else {
manager.updateAppWidget(new ComponentName(context, this.getClass()), views);
}
}
/**
* Handle a change notification coming over from {@link DownloadService}
*/
public void notifyChange(Context context, DownloadService service, boolean playing) {
if (hasInstances(context)) {
performUpdate(context, service, null, playing);
}
}
/**
* Check against {@link AppWidgetManager} if there are any instances of this widget.
*/
private boolean hasInstances(Context context) {
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass()));
return (appWidgetIds.length > 0);
}
/**
* Update all active widget instances by pushing changes
*/
private void performUpdate(Context context, DownloadService service, int[] appWidgetIds, boolean playing) {
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
MusicDirectory.Entry currentPlaying = service.getCurrentPlaying() == null ? null : service.getCurrentPlaying().getSong();
String title = currentPlaying == null ? null : currentPlaying.getTitle();
CharSequence artist = currentPlaying == null ? null : currentPlaying.getArtist();
CharSequence errorState = null;
// Show error message?
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_SHARED) ||
status.equals(Environment.MEDIA_UNMOUNTED)) {
errorState = res.getText(R.string.widget_sdcard_busy);
} else if (status.equals(Environment.MEDIA_REMOVED)) {
errorState = res.getText(R.string.widget_sdcard_missing);
} else if (currentPlaying == null) {
errorState = res.getText(R.string.widget_initial_text);
}
if (errorState != null) {
// Show error state to user
views.setTextViewText(R.id.title,null);
views.setTextViewText(R.id.artist, errorState);
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_default);
} else {
// No error, so show normal titles
views.setTextViewText(R.id.title, title);
views.setTextViewText(R.id.artist, artist);
}
// Set correct drawable for pause state
if (playing) {
views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_pause);
} else {
views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_play);
}
// Set the cover art
try {
int size = context.getResources().getDrawable(R.drawable.appwidget_art_default).getIntrinsicHeight();
Bitmap bitmap = currentPlaying == null ? null : FileUtil.getAlbumArtBitmap(context, currentPlaying, size);
if (bitmap == null) {
// Set default cover art
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown);
} else {
bitmap = getRoundedCornerBitmap(bitmap);
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap);
}
} catch (Exception x) {
Log.e(TAG, "Failed to load cover art", x);
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown);
}
// Link actions buttons to intents
linkButtons(context, views, currentPlaying != null);
pushUpdate(context, appWidgetIds, views);
}
/**
* Round the corners of a bitmap for the cover art image
*/
private static Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final float roundPx = 10;
// Add extra width to the rect so the right side wont be rounded.
final Rect rect = new Rect(0, 0, bitmap.getWidth() + (int) roundPx, bitmap.getHeight());
final RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
/**
* Link up various button actions using {@link PendingIntent}.
*
* @param playerActive True if player is active in background, which means
* widget click will launch {@link DownloadActivity},
* otherwise we launch {@link MainActivity}.
*/
private void linkButtons(Context context, RemoteViews views, boolean playerActive) {
Intent intent = new Intent(context, playerActive ? DownloadActivity.class : MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent);
views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent);
// Emulate media button clicks.
intent = new Intent("1");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
intent = new Intent("2"); // Use a unique action name to ensure a different PendingIntent to be created.
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
intent = new Intent("3"); // Use a unique action name to ensure a different PendingIntent to be created.
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
}
}