Home | History | Annotate | Download | only in music
      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     private static MediaAppWidgetProvider sInstance;
     40 
     41     static synchronized MediaAppWidgetProvider getInstance() {
     42         if (sInstance == null) {
     43             sInstance = new MediaAppWidgetProvider();
     44         }
     45         return sInstance;
     46     }
     47 
     48     @Override
     49     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
     50         defaultAppWidget(context, appWidgetIds);
     51 
     52         // Send broadcast intent to any running MediaPlaybackService so it can
     53         // wrap around with an immediate update.
     54         Intent updateIntent = new Intent(MediaPlaybackService.SERVICECMD);
     55         updateIntent.putExtra(MediaPlaybackService.CMDNAME,
     56                 MediaAppWidgetProvider.CMDAPPWIDGETUPDATE);
     57         updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
     58         updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
     59         context.sendBroadcast(updateIntent);
     60     }
     61 
     62     /**
     63      * Initialize given widgets to default state, where we launch Music on default click
     64      * and hide actions if service not running.
     65      */
     66     private void defaultAppWidget(Context context, int[] appWidgetIds) {
     67         final Resources res = context.getResources();
     68         final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.album_appwidget);
     69 
     70         views.setViewVisibility(R.id.title, View.GONE);
     71         views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
     72 
     73         linkButtons(context, views, false /* not playing */);
     74         pushUpdate(context, appWidgetIds, views);
     75     }
     76 
     77     private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {
     78         // Update specific list of appWidgetIds if given, otherwise default to all
     79         final AppWidgetManager gm = AppWidgetManager.getInstance(context);
     80         if (appWidgetIds != null) {
     81             gm.updateAppWidget(appWidgetIds, views);
     82         } else {
     83             gm.updateAppWidget(new ComponentName(context, this.getClass()), views);
     84         }
     85     }
     86 
     87     /**
     88      * Check against {@link AppWidgetManager} if there are any instances of this widget.
     89      */
     90     private boolean hasInstances(Context context) {
     91         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
     92         int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
     93                 new ComponentName(context, this.getClass()));
     94         return (appWidgetIds.length > 0);
     95     }
     96 
     97     /**
     98      * Handle a change notification coming over from {@link MediaPlaybackService}
     99      */
    100     void notifyChange(MediaPlaybackService service, String what) {
    101         if (hasInstances(service)) {
    102             if (MediaPlaybackService.META_CHANGED.equals(what) ||
    103                     MediaPlaybackService.PLAYSTATE_CHANGED.equals(what)) {
    104                 performUpdate(service, null);
    105             }
    106         }
    107     }
    108 
    109     /**
    110      * Update all active widget instances by pushing changes
    111      */
    112     void performUpdate(MediaPlaybackService service, int[] appWidgetIds) {
    113         final Resources res = service.getResources();
    114         final RemoteViews views = new RemoteViews(service.getPackageName(), R.layout.album_appwidget);
    115 
    116         CharSequence titleName = service.getTrackName();
    117         CharSequence artistName = service.getArtistName();
    118         CharSequence errorState = null;
    119 
    120         // Format title string with track number, or show SD card message
    121         String status = Environment.getExternalStorageState();
    122         if (status.equals(Environment.MEDIA_SHARED) ||
    123                 status.equals(Environment.MEDIA_UNMOUNTED)) {
    124             if (android.os.Environment.isExternalStorageRemovable()) {
    125                 errorState = res.getText(R.string.sdcard_busy_title);
    126             } else {
    127                 errorState = res.getText(R.string.sdcard_busy_title_nosdcard);
    128             }
    129         } else if (status.equals(Environment.MEDIA_REMOVED)) {
    130             if (android.os.Environment.isExternalStorageRemovable()) {
    131                 errorState = res.getText(R.string.sdcard_missing_title);
    132             } else {
    133                 errorState = res.getText(R.string.sdcard_missing_title_nosdcard);
    134             }
    135         } else if (titleName == null) {
    136             errorState = res.getText(R.string.emptyplaylist);
    137         }
    138 
    139         if (errorState != null) {
    140             // Show error state to user
    141             views.setViewVisibility(R.id.title, View.GONE);
    142             views.setTextViewText(R.id.artist, errorState);
    143 
    144         } else {
    145             // No error, so show normal titles
    146             views.setViewVisibility(R.id.title, View.VISIBLE);
    147             views.setTextViewText(R.id.title, titleName);
    148             views.setTextViewText(R.id.artist, artistName);
    149         }
    150 
    151         // Set correct drawable for pause state
    152         final boolean playing = service.isPlaying();
    153         if (playing) {
    154             views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_pause);
    155         } else {
    156             views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_play);
    157         }
    158 
    159         // Link actions buttons to intents
    160         linkButtons(service, views, playing);
    161 
    162         pushUpdate(service, appWidgetIds, views);
    163     }
    164 
    165     /**
    166      * Link up various button actions using {@link PendingIntents}.
    167      *
    168      * @param playerActive True if player is active in background, which means
    169      *            widget click will launch {@link MediaPlaybackActivity},
    170      *            otherwise we launch {@link MusicBrowserActivity}.
    171      */
    172     private void linkButtons(Context context, RemoteViews views, boolean playerActive) {
    173         // Connect up various buttons and touch events
    174         Intent intent;
    175         PendingIntent pendingIntent;
    176 
    177         final ComponentName serviceName = new ComponentName(context, MediaPlaybackService.class);
    178 
    179         if (playerActive) {
    180             intent = new Intent(context, MediaPlaybackActivity.class);
    181             pendingIntent = PendingIntent.getActivity(context,
    182                     0 /* no requestCode */, intent, 0 /* no flags */);
    183             views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent);
    184         } else {
    185             intent = new Intent(context, MusicBrowserActivity.class);
    186             pendingIntent = PendingIntent.getActivity(context,
    187                     0 /* no requestCode */, intent, 0 /* no flags */);
    188             views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent);
    189         }
    190 
    191         intent = new Intent(MediaPlaybackService.TOGGLEPAUSE_ACTION);
    192         intent.setComponent(serviceName);
    193         pendingIntent = PendingIntent.getService(context,
    194                 0 /* no requestCode */, intent, 0 /* no flags */);
    195         views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
    196 
    197         intent = new Intent(MediaPlaybackService.NEXT_ACTION);
    198         intent.setComponent(serviceName);
    199         pendingIntent = PendingIntent.getService(context,
    200                 0 /* no requestCode */, intent, 0 /* no flags */);
    201         views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
    202     }
    203 }
    204