این وبسایت یک نسخه آزمایشی از سایت developer android می‌باشد که در حال ترجمه می‌باشد.
برای پیوستن به تیم ترجمه و خواندن مستندات اینجا کلیک کنید.
MediaBrowserService / src / com.example.android.mediabrowserservice /

MediaNotification.java

1
/*
2
 * Copyright (C) 2014 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.example.android.mediabrowserservice;
18
 
19
import android.app.Notification;
20
import android.app.NotificationManager;
21
import android.app.PendingIntent;
22
import android.content.BroadcastReceiver;
23
import android.content.Context;
24
import android.content.Intent;
25
import android.content.IntentFilter;
26
import android.content.pm.ApplicationInfo;
27
import android.content.pm.PackageManager;
28
import android.content.res.Resources;
29
import android.content.res.TypedArray;
30
import android.graphics.Bitmap;
31
import android.graphics.BitmapFactory;
32
import android.graphics.Color;
33
import android.media.MediaDescription;
34
import android.media.MediaMetadata;
35
import android.media.session.MediaController;
36
import android.media.session.MediaSession;
37
import android.media.session.PlaybackState;
38
import android.os.AsyncTask;
39
import android.util.LruCache;
40
 
41
import com.example.android.mediabrowserservice.utils.BitmapHelper;
42
import com.example.android.mediabrowserservice.utils.LogHelper;
43
 
44
import java.io.IOException;
45
 
46
/**
47
 * Keeps track of a notification and updates it automatically for a given
48
 * MediaSession. Maintaining a visible notification (usually) guarantees that the music service
49
 * won't be killed during playback.
50
 */
51
public class MediaNotification extends BroadcastReceiver {
52
    private static final String TAG = "MediaNotification";
53
 
54
    private static final int NOTIFICATION_ID = 412;
55
 
56
    public static final String ACTION_PAUSE = "com.example.android.mediabrowserservice.pause";
57
    public static final String ACTION_PLAY = "com.example.android.mediabrowserservice.play";
58
    public static final String ACTION_PREV = "com.example.android.mediabrowserservice.prev";
59
    public static final String ACTION_NEXT = "com.example.android.mediabrowserservice.next";
60
 
61
    private static final int MAX_ALBUM_ART_CACHE_SIZE = 1024*1024;
62
 
63
    private final MusicService mService;
64
    private MediaSession.Token mSessionToken;
65
    private MediaController mController;
66
    private MediaController.TransportControls mTransportControls;
67
    private final LruCache<String, Bitmap> mAlbumArtCache;
68
 
69
    private PlaybackState mPlaybackState;
70
    private MediaMetadata mMetadata;
71
 
72
    private Notification.Builder mNotificationBuilder;
73
    private NotificationManager mNotificationManager;
74
    private Notification.Action mPlayPauseAction;
75
 
76
    private PendingIntent mPauseIntent, mPlayIntent, mPreviousIntent, mNextIntent;
77
 
78
    private String mCurrentAlbumArt;
79
    private int mNotificationColor;
80
 
81
    private boolean mStarted = false;
82
 
83
    public MediaNotification(MusicService service) {
84
        mService = service;
85
        updateSessionToken();
86
 
87
        // simple album art cache that holds no more than
88
        // MAX_ALBUM_ART_CACHE_SIZE bytes:
89
        mAlbumArtCache = new LruCache<String, Bitmap>(MAX_ALBUM_ART_CACHE_SIZE) {
90
            @Override
91
            protected int sizeOf(String key, Bitmap value) {
92
                return value.getByteCount();
93
            }
94
        };
95
 
96
        mNotificationColor = getNotificationColor();
97
 
98
        mNotificationManager = (NotificationManager) mService
99
                .getSystemService(Context.NOTIFICATION_SERVICE);
100
 
101
        String pkg = mService.getPackageName();
102
        mPauseIntent = PendingIntent.getBroadcast(mService, 100,
103
                new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
104
        mPlayIntent = PendingIntent.getBroadcast(mService, 100,
105
                new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
106
        mPreviousIntent = PendingIntent.getBroadcast(mService, 100,
107
                new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
108
        mNextIntent = PendingIntent.getBroadcast(mService, 100,
109
                new Intent(ACTION_NEXT).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
110
    }
111
 
112
    protected int getNotificationColor() {
113
        int notificationColor = 0;
114
        String packageName = mService.getPackageName();
115
        try {
116
            Context packageContext = mService.createPackageContext(packageName, 0);
117
            ApplicationInfo applicationInfo =
118
                    mService.getPackageManager().getApplicationInfo(packageName, 0);
119
            packageContext.setTheme(applicationInfo.theme);
120
            Resources.Theme theme = packageContext.getTheme();
121
            TypedArray ta = theme.obtainStyledAttributes(
122
                    new int[] {android.R.attr.colorPrimary});
123
            notificationColor = ta.getColor(0, Color.DKGRAY);
124
            ta.recycle();
125
        } catch (PackageManager.NameNotFoundException e) {
126
            e.printStackTrace();
127
        }
128
        return notificationColor;
129
    }
130
 
131
    /**
132
     * Posts the notification and starts tracking the session to keep it
133
     * updated. The notification will automatically be removed if the session is
134
     * destroyed before {@link #stopNotification} is called.
135
     */
136
    public void startNotification() {
137
        if (!mStarted) {
138
            mController.registerCallback(mCb);
139
            IntentFilter filter = new IntentFilter();
140
            filter.addAction(ACTION_NEXT);
141
            filter.addAction(ACTION_PAUSE);
142
            filter.addAction(ACTION_PLAY);
143
            filter.addAction(ACTION_PREV);
144
            mService.registerReceiver(this, filter);
145
 
146
            mMetadata = mController.getMetadata();
147
            mPlaybackState = mController.getPlaybackState();
148
 
149
            mStarted = true;
150
            // The notification must be updated after setting started to true
151
            updateNotificationMetadata();
152
        }
153
    }
154
 
155
    /**
156
     * Removes the notification and stops tracking the session. If the session
157
     * was destroyed this has no effect.
158
     */
159
    public void stopNotification() {
160
        mStarted = false;
161
        mController.unregisterCallback(mCb);
162
        try {
163
            mNotificationManager.cancel(NOTIFICATION_ID);
164
            mService.unregisterReceiver(this);
165
        } catch (IllegalArgumentException ex) {
166
            // ignore if the receiver is not registered.
167
        }
168
        mService.stopForeground(true);
169
    }
170
 
171
    @Override
172
    public void onReceive(Context context, Intent intent) {
173
        final String action = intent.getAction();
174
        LogHelper.d(TAG, "Received intent with action " + action);
175
        if (ACTION_PAUSE.equals(action)) {
176
            mTransportControls.pause();
177
        } else if (ACTION_PLAY.equals(action)) {
178
            mTransportControls.play();
179
        } else if (ACTION_NEXT.equals(action)) {
180
            mTransportControls.skipToNext();
181
        } else if (ACTION_PREV.equals(action)) {
182
            mTransportControls.skipToPrevious();
183
        }
184
    }
185
 
186
    /**
187
     * Update the state based on a change on the session token. Called either when
188
     * we are running for the first time or when the media session owner has destroyed the session
189
     * (see {@link android.media.session.MediaController.Callback#onSessionDestroyed()})
190
     */
191
    private void updateSessionToken() {
192
        MediaSession.Token freshToken = mService.getSessionToken();
193
        if (mSessionToken == null || !mSessionToken.equals(freshToken)) {
194
            if (mController != null) {
195
                mController.unregisterCallback(mCb);
196
            }
197
            mSessionToken = freshToken;
198
            mController = new MediaController(mService, mSessionToken);
199
            mTransportControls = mController.getTransportControls();
200
            if (mStarted) {
201
                mController.registerCallback(mCb);
202
            }
203
        }
204
    }
205
 
206
    private final MediaController.Callback mCb = new MediaController.Callback() {
207
        @Override
208
        public void onPlaybackStateChanged(PlaybackState state) {
209
            mPlaybackState = state;
210
            LogHelper.d(TAG, "Received new playback state", state);
211
            updateNotificationPlaybackState();
212
        }
213
 
214
        @Override
215
        public void onMetadataChanged(MediaMetadata metadata) {
216
            mMetadata = metadata;
217
            LogHelper.d(TAG, "Received new metadata ", metadata);
218
            updateNotificationMetadata();
219
        }
220
 
221
        @Override
222
        public void onSessionDestroyed() {
223
            super.onSessionDestroyed();
224
            LogHelper.d(TAG, "Session was destroyed, resetting to the new session token");
225
            updateSessionToken();
226
        }
227
    };
228
 
229
    private void updateNotificationMetadata() {
230
        LogHelper.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata);
231
        if (mMetadata == null || mPlaybackState == null) {
232
            return;
233
        }
234
 
235
        updatePlayPauseAction();
236
 
237
        mNotificationBuilder = new Notification.Builder(mService);
238
        int playPauseActionIndex = 0;
239
 
240
        // If skip to previous action is enabled
241
        if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
242
            mNotificationBuilder
243
                    .addAction(R.drawable.ic_skip_previous_white_24dp,
244
                            mService.getString(R.string.label_previous), mPreviousIntent);
245
            playPauseActionIndex = 1;
246
        }
247
 
248
        mNotificationBuilder.addAction(mPlayPauseAction);
249
 
250
        // If skip to next action is enabled
251
        if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
252
            mNotificationBuilder.addAction(R.drawable.ic_skip_next_white_24dp,
253
                    mService.getString(R.string.label_next), mNextIntent);
254
        }
255
 
256
        MediaDescription description = mMetadata.getDescription();
257
 
258
        String fetchArtUrl = null;
259
        Bitmap art = description.getIconBitmap();
260
        if (art == null && description.getIconUri() != null) {
261
            // This sample assumes the iconUri will be a valid URL formatted String, but
262
            // it can actually be any valid Android Uri formatted String.
263
            // async fetch the album art icon
264
            String artUrl = description.getIconUri().toString();
265
            art = mAlbumArtCache.get(artUrl);
266
            if (art == null) {
267
                fetchArtUrl = artUrl;
268
                // use a placeholder art while the remote art is being downloaded
269
                art = BitmapFactory.decodeResource(mService.getResources(), R.drawable.ic_default_art);
270
            }
271
        }
272
 
273
        mNotificationBuilder
274
                .setStyle(new Notification.MediaStyle()
275
                        .setShowActionsInCompactView(playPauseActionIndex)  // only show play/pause in compact view
276
                        .setMediaSession(mSessionToken))
277
                .setColor(mNotificationColor)
278
                .setSmallIcon(R.drawable.ic_notification)
279
                .setVisibility(Notification.VISIBILITY_PUBLIC)
280
                .setUsesChronometer(true)
281
                .setContentTitle(description.getTitle())
282
                .setContentText(description.getSubtitle())
283
                .setLargeIcon(art);
284
 
285
        updateNotificationPlaybackState();
286
 
287
        mService.startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
288
        if (fetchArtUrl != null) {
289
            fetchBitmapFromURLAsync(fetchArtUrl);
290
        }
291
    }
292
 
293
    private void updatePlayPauseAction() {
294
        LogHelper.d(TAG, "updatePlayPauseAction");
295
        String label;
296
        int icon;
297
        PendingIntent intent;
298
        if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING) {
299
            label = mService.getString(R.string.label_pause);
300
            icon = R.drawable.ic_pause_white_24dp;
301
            intent = mPauseIntent;
302
        } else {
303
            label = mService.getString(R.string.label_play);
304
            icon = R.drawable.ic_play_arrow_white_24dp;
305
            intent = mPlayIntent;
306
        }
307
        if (mPlayPauseAction == null) {
308
            mPlayPauseAction = new Notification.Action(icon, label, intent);
309
        } else {
310
            mPlayPauseAction.icon = icon;
311
            mPlayPauseAction.title = label;
312
            mPlayPauseAction.actionIntent = intent;
313
        }
314
    }
315
 
316
    private void updateNotificationPlaybackState() {
317
        LogHelper.d(TAG, "updateNotificationPlaybackState. mPlaybackState=" + mPlaybackState);
318
        if (mPlaybackState == null || !mStarted) {
319
            LogHelper.d(TAG, "updateNotificationPlaybackState. cancelling notification!");
320
            mService.stopForeground(true);
321
            return;
322
        }
323
        if (mNotificationBuilder == null) {
324
            LogHelper.d(TAG, "updateNotificationPlaybackState. there is no notificationBuilder. Ignoring request to update state!");
325
            return;
326
        }
327
        if (mPlaybackState.getPosition() >= 0) {
328
            LogHelper.d(TAG, "updateNotificationPlaybackState. updating playback position to ",
329
                    (System.currentTimeMillis() - mPlaybackState.getPosition()) / 1000, " seconds");
330
            mNotificationBuilder
331
                    .setWhen(System.currentTimeMillis() - mPlaybackState.getPosition())
332
                    .setShowWhen(true)
333
                    .setUsesChronometer(true);
334
            mNotificationBuilder.setShowWhen(true);
335
        } else {
336
            LogHelper.d(TAG, "updateNotificationPlaybackState. hiding playback position");
337
            mNotificationBuilder
338
                    .setWhen(0)
339
                    .setShowWhen(false)
340
                    .setUsesChronometer(false);
341
        }
342
 
343
        updatePlayPauseAction();
344
 
345
        // Make sure that the notification can be dismissed by the user when we are not playing:
346
        mNotificationBuilder.setOngoing(mPlaybackState.getState() == PlaybackState.STATE_PLAYING);
347
 
348
        mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
349
    }
350
 
351
    public void fetchBitmapFromURLAsync(final String source) {
352
        LogHelper.d(TAG, "getBitmapFromURLAsync: starting asynctask to fetch ", source);
353
        new AsyncTask<Void, Void, Bitmap>() {
354
            @Override
355
            protected Bitmap doInBackground(Void[] objects) {
356
                Bitmap bitmap = null;
357
                try {
358
                    bitmap = BitmapHelper.fetchAndRescaleBitmap(source,
359
                            BitmapHelper.MEDIA_ART_BIG_WIDTH, BitmapHelper.MEDIA_ART_BIG_HEIGHT);
360
                    mAlbumArtCache.put(source, bitmap);
361
                } catch (IOException e) {
362
                    LogHelper.e(TAG, e, "getBitmapFromURLAsync: " + source);
363
                }
364
                return bitmap;
365
            }
366
 
367
            @Override
368
            protected void onPostExecute(Bitmap bitmap) {
369
                if (bitmap != null && mMetadata != null &&
370
                        mNotificationBuilder != null && mMetadata.getDescription() != null &&
371
                        !source.equals(mMetadata.getDescription().getIconUri())) {
372
                    // If the media is still the same, update the notification:
373
                    LogHelper.d(TAG, "getBitmapFromURLAsync: set bitmap to ", source);
374
                    mNotificationBuilder.setLargeIcon(bitmap);
375