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("Track title") 159 * .setContentText("Artist - Album") 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