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