1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.music; 18 19 import android.app.PendingIntent; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProvider; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.os.Environment; 27 import android.view.View; 28 import android.widget.RemoteViews; 29 30 /** 31 * Simple widget to show currently playing album art along 32 * with play/pause and next track buttons. 33 */ 34 public class MediaAppWidgetProvider extends AppWidgetProvider { 35 static final String TAG = "MusicAppWidgetProvider"; 36 37 public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate"; 38 39 static final ComponentName THIS_APPWIDGET = 40 new ComponentName("com.android.music", 41 "com.android.music.MediaAppWidgetProvider"); 42 43 private static MediaAppWidgetProvider sInstance; 44 getInstance()45 static synchronized MediaAppWidgetProvider getInstance() { 46 if (sInstance == null) { 47 sInstance = new MediaAppWidgetProvider(); 48 } 49 return sInstance; 50 } 51 52 @Override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)53 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 54 defaultAppWidget(context, appWidgetIds); 55 56 // Send broadcast intent to any running MediaPlaybackService so it can 57 // wrap around with an immediate update. 58 Intent updateIntent = new Intent(MediaPlaybackService.SERVICECMD); 59 updateIntent.putExtra(MediaPlaybackService.CMDNAME, 60 MediaAppWidgetProvider.CMDAPPWIDGETUPDATE); 61 updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); 62 updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 63 context.sendBroadcast(updateIntent); 64 } 65 66 /** 67 * Initialize given widgets to default state, where we launch Music on default click 68 * and hide actions if service not running. 69 */ defaultAppWidget(Context context, int[] appWidgetIds)70 private void defaultAppWidget(Context context, int[] appWidgetIds) { 71 final Resources res = context.getResources(); 72 final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.album_appwidget); 73 74 views.setViewVisibility(R.id.title, View.GONE); 75 views.setTextViewText(R.id.artist, res.getText(R.string.emptyplaylist)); 76 77 linkButtons(context, views, false /* not playing */); 78 pushUpdate(context, appWidgetIds, views); 79 } 80 pushUpdate(Context context, int[] appWidgetIds, RemoteViews views)81 private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) { 82 // Update specific list of appWidgetIds if given, otherwise default to all 83 final AppWidgetManager gm = AppWidgetManager.getInstance(context); 84 if (appWidgetIds != null) { 85 gm.updateAppWidget(appWidgetIds, views); 86 } else { 87 gm.updateAppWidget(THIS_APPWIDGET, views); 88 } 89 } 90 91 /** 92 * Check against {@link AppWidgetManager} if there are any instances of this widget. 93 */ hasInstances(Context context)94 private boolean hasInstances(Context context) { 95 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 96 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(THIS_APPWIDGET); 97 return (appWidgetIds.length > 0); 98 } 99 100 /** 101 * Handle a change notification coming over from {@link MediaPlaybackService} 102 */ notifyChange(MediaPlaybackService service, String what)103 void notifyChange(MediaPlaybackService service, String what) { 104 if (hasInstances(service)) { 105 if (MediaPlaybackService.PLAYBACK_COMPLETE.equals(what) || 106 MediaPlaybackService.META_CHANGED.equals(what) || 107 MediaPlaybackService.PLAYSTATE_CHANGED.equals(what)) { 108 performUpdate(service, null); 109 } 110 } 111 } 112 113 /** 114 * Update all active widget instances by pushing changes 115 */ performUpdate(MediaPlaybackService service, int[] appWidgetIds)116 void performUpdate(MediaPlaybackService service, int[] appWidgetIds) { 117 final Resources res = service.getResources(); 118 final RemoteViews views = new RemoteViews(service.getPackageName(), R.layout.album_appwidget); 119 120 CharSequence titleName = service.getTrackName(); 121 CharSequence artistName = service.getArtistName(); 122 CharSequence errorState = null; 123 124 // Format title string with track number, or show SD card message 125 String status = Environment.getExternalStorageState(); 126 if (status.equals(Environment.MEDIA_SHARED) || 127 status.equals(Environment.MEDIA_UNMOUNTED)) { 128 errorState = res.getText(R.string.sdcard_busy_title); 129 } else if (status.equals(Environment.MEDIA_REMOVED)) { 130 errorState = res.getText(R.string.sdcard_missing_title); 131 } else if (titleName == null) { 132 errorState = res.getText(R.string.emptyplaylist); 133 } 134 135 if (errorState != null) { 136 // Show error state to user 137 views.setViewVisibility(R.id.title, View.GONE); 138 views.setTextViewText(R.id.artist, errorState); 139 140 } else { 141 // No error, so show normal titles 142 views.setViewVisibility(R.id.title, View.VISIBLE); 143 views.setTextViewText(R.id.title, titleName); 144 views.setTextViewText(R.id.artist, artistName); 145 } 146 147 // Set correct drawable for pause state 148 final boolean playing = service.isPlaying(); 149 if (playing) { 150 views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_pause); 151 } else { 152 views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_play); 153 } 154 155 // Link actions buttons to intents 156 linkButtons(service, views, playing); 157 158 pushUpdate(service, appWidgetIds, views); 159 } 160 161 /** 162 * Link up various button actions using {@link PendingIntents}. 163 * 164 * @param playerActive True if player is active in background, which means 165 * widget click will launch {@link MediaPlaybackActivity}, 166 * otherwise we launch {@link MusicBrowserActivity}. 167 */ linkButtons(Context context, RemoteViews views, boolean playerActive)168 private void linkButtons(Context context, RemoteViews views, boolean playerActive) { 169 // Connect up various buttons and touch events 170 Intent intent; 171 PendingIntent pendingIntent; 172 173 final ComponentName serviceName = new ComponentName(context, MediaPlaybackService.class); 174 175 if (playerActive) { 176 intent = new Intent(context, MediaPlaybackActivity.class); 177 pendingIntent = PendingIntent.getActivity(context, 178 0 /* no requestCode */, intent, 0 /* no flags */); 179 views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent); 180 } else { 181 intent = new Intent(context, MusicBrowserActivity.class); 182 pendingIntent = PendingIntent.getActivity(context, 183 0 /* no requestCode */, intent, 0 /* no flags */); 184 views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent); 185 } 186 187 intent = new Intent(MediaPlaybackService.TOGGLEPAUSE_ACTION); 188 intent.setComponent(serviceName); 189 pendingIntent = PendingIntent.getService(context, 190 0 /* no requestCode */, intent, 0 /* no flags */); 191 views.setOnClickPendingIntent(R.id.control_play, pendingIntent); 192 193 intent = new Intent(MediaPlaybackService.NEXT_ACTION); 194 intent.setComponent(serviceName); 195 pendingIntent = PendingIntent.getService(context, 196 0 /* no requestCode */, intent, 0 /* no flags */); 197 views.setOnClickPendingIntent(R.id.control_next, pendingIntent); 198 } 199 } 200