1/*
2 * Copyright (C) 2015 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
17package android.support.v7.app;
18
19import android.app.Notification;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.os.Build;
23import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
24import android.support.v4.media.session.MediaSessionCompat;
25import android.support.v7.internal.app.NotificationCompatImpl21;
26import android.support.v7.internal.app.NotificationCompatImplBase;
27
28/**
29 * An extension of {@link android.support.v4.app.NotificationCompat} which supports
30 * {@link android.support.v7.app.NotificationCompat.MediaStyle}. You should start using this variant
31 * if you need support for media styled notifications.
32 */
33public class NotificationCompat extends android.support.v4.app.NotificationCompat {
34
35    private static void addMediaStyleToBuilderLollipop(
36            NotificationBuilderWithBuilderAccessor builder, android.support.v4.app.NotificationCompat.Style style) {
37        if (style instanceof MediaStyle) {
38            MediaStyle mediaStyle = (MediaStyle) style;
39            NotificationCompatImpl21.addMediaStyle(builder,
40                    mediaStyle.mActionsToShowInCompact,
41                    mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
42        }
43    }
44
45    private static void addMediaStyleToBuilderIcs(NotificationBuilderWithBuilderAccessor builder,
46            android.support.v4.app.NotificationCompat.Builder b) {
47        if (b.mStyle instanceof MediaStyle) {
48            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
49            NotificationCompatImplBase.overrideContentView(builder, b.mContext,
50                    b.mContentTitle,
51                    b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon, b.mSubText,
52                    b.mUseChronometer, b.mNotification.when, b.mActions,
53                    mediaStyle.mActionsToShowInCompact, mediaStyle.mShowCancelButton,
54                    mediaStyle.mCancelButtonIntent);
55        }
56    }
57
58    private static void addBigMediaStyleToBuilderJellybean(Notification n,
59            android.support.v4.app.NotificationCompat.Builder b) {
60        if (b.mStyle instanceof MediaStyle) {
61            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
62            NotificationCompatImplBase.overrideBigContentView(n, b.mContext,
63                    b.mContentTitle,
64                    b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon, b.mSubText,
65                    b.mUseChronometer, b.mNotification.when, b.mActions,
66                    mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent);
67        }
68    }
69
70    /**
71     * See {@link android.support.v4.app.NotificationCompat}. In addition to the builder in v4, this
72     * builder also supports {@link MediaStyle}.
73     */
74    public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
75
76        /**
77         * @inheritDoc
78         */
79        public Builder(Context context) {
80            super(context);
81        }
82
83        @Override
84        protected BuilderExtender getExtender() {
85            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
86                return new LollipopExtender();
87            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
88                return new JellybeanExtender();
89            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
90                return new IceCreamSandwichExtender();
91            } else {
92                return super.getExtender();
93            }
94        }
95    }
96
97    private static class IceCreamSandwichExtender extends BuilderExtender {
98
99        @Override
100        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
101                NotificationBuilderWithBuilderAccessor builder) {
102            addMediaStyleToBuilderIcs(builder, b);
103            return builder.build();
104        }
105    }
106
107    private static class JellybeanExtender extends BuilderExtender {
108
109        @Override
110        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
111                NotificationBuilderWithBuilderAccessor builder) {
112            addMediaStyleToBuilderIcs(builder, b);
113            Notification n = builder.build();
114            addBigMediaStyleToBuilderJellybean(n, b);
115            return n;
116        }
117    }
118
119    private static class LollipopExtender extends BuilderExtender {
120
121        @Override
122        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
123                NotificationBuilderWithBuilderAccessor builder) {
124            addMediaStyleToBuilderLollipop(builder, b.mStyle);
125            return builder.build();
126        }
127    }
128
129    /**
130     * Notification style for media playback notifications.
131     *
132     * In the expanded form, {@link Notification#bigContentView}, up to 5
133     * {@link android.support.v4.app.NotificationCompat.Action}s specified with
134     * {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent) addAction} will
135     * be shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
136     * {@link NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will
137     * be treated as album artwork.
138     *
139     * Unlike the other styles provided here, MediaStyle can also modify the standard-size
140     * {@link Notification#contentView}; by providing action indices to
141     * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
142     * in the standard view alongside the usual content.
143     *
144     * Notifications created with MediaStyle will have their category set to
145     * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
146     * category using {@link NotificationCompat.Builder#setCategory(String) setCategory()}.
147     *
148     * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
149     * {@link android.support.v7.app.NotificationCompat.MediaStyle#setMediaSession}, the System UI
150     * can identify this as a notification representing an active media session and respond
151     * accordingly (by showing album artwork in the lockscreen, for example).
152     *
153     * To use this style with your Notification, feed it to
154     * {@link NotificationCompat.Builder#setStyle} like so:
155     * <pre class="prettyprint">
156     * Notification noti = new NotificationCompat.Builder()
157     *     .setSmallIcon(R.drawable.ic_stat_player)
158     *     .setContentTitle(&quot;Track title&quot;)
159     *     .setContentText(&quot;Artist - Album&quot;)
160     *     .setLargeIcon(albumArtBitmap))
161     *     .setStyle(<b>new NotificationCompat.MediaStyle()</b>
162     *         .setMediaSession(mySession))
163     *     .build();
164     * </pre>
165     *
166     * @see Notification#bigContentView
167     */
168    public static class MediaStyle extends android.support.v4.app.NotificationCompat.Style {
169
170        int[] mActionsToShowInCompact = null;
171        MediaSessionCompat.Token mToken;
172        boolean mShowCancelButton;
173        PendingIntent mCancelButtonIntent;
174
175        public MediaStyle() {
176        }
177
178        public MediaStyle(android.support.v4.app.NotificationCompat.Builder builder) {
179            setBuilder(builder);
180        }
181
182        /**
183         * Request up to 3 actions (by index in the order of addition) to be shown in the compact
184         * notification view.
185         *
186         * @param actions the indices of the actions to show in the compact notification view
187         */
188        public MediaStyle setShowActionsInCompactView(int...actions) {
189            mActionsToShowInCompact = actions;
190            return this;
191        }
192
193        /**
194         * Attach a {@link MediaSessionCompat.Token} to this Notification
195         * to provide additional playback information and control to the SystemUI.
196         */
197        public MediaStyle setMediaSession(MediaSessionCompat.Token token) {
198            mToken = token;
199            return this;
200        }
201
202        /**
203         * Sets whether a cancel button at the top right should be shown in the notification on
204         * platforms before Lollipop.
205         *
206         * <p>Prior to Lollipop, there was a bug in the framework which prevented the developer to
207         * make a notification dismissable again after having used the same notification as the
208         * ongoing notification for a foreground service. When the notification was posted by
209         * {@link android.app.Service#startForeground}, but then the service exited foreground mode
210         * via {@link android.app.Service#stopForeground}, without removing the notification, the
211         * notification stayed ongoing, and thus not dismissable.
212         *
213         * <p>This is a common scenario for media notifications, as this is exactly the service
214         * lifecycle that happens when playing/pausing media. Thus, a workaround is provided by the
215         * support library: Instead of making the notification ongoing depending on the playback
216         * state, the support library provides the ability to add an explicit cancel button to the
217         * notification.
218         *
219         * <p>Note that the notification is enforced to be ongoing if a cancel button is shown to
220         * provide a consistent user experience.
221         *
222         * <p>Also note that this method is a no-op when running on Lollipop and later.
223         *
224         * @param show whether to show a cancel button
225         */
226        public MediaStyle setShowCancelButton(boolean show) {
227            mShowCancelButton = show;
228            return this;
229        }
230
231        /**
232         * Sets the pending intent to be sent when the cancel button is pressed. See {@link
233         * #setShowCancelButton}.
234         *
235         * @param pendingIntent the intent to be sent when the cancel button is pressed
236         */
237        public MediaStyle setCancelButtonIntent(PendingIntent pendingIntent) {
238            mCancelButtonIntent = pendingIntent;
239            return this;
240        }
241    }
242}
243