1/* 2 * Copyright (C) 2017 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.v4.app; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20import static android.support.v4.app.NotificationCompat.DEFAULT_SOUND; 21import static android.support.v4.app.NotificationCompat.DEFAULT_VIBRATE; 22import static android.support.v4.app.NotificationCompat.FLAG_GROUP_SUMMARY; 23import static android.support.v4.app.NotificationCompat.GROUP_ALERT_ALL; 24import static android.support.v4.app.NotificationCompat.GROUP_ALERT_CHILDREN; 25import static android.support.v4.app.NotificationCompat.GROUP_ALERT_SUMMARY; 26 27import android.app.Notification; 28import android.os.Build; 29import android.os.Bundle; 30import android.support.annotation.RestrictTo; 31import android.util.SparseArray; 32import android.widget.RemoteViews; 33 34import java.util.ArrayList; 35import java.util.List; 36 37/** 38 * Wrapper around {@link Notification.Builder} that works in a backwards compatible way. 39 * 40 * @hide 41 */ 42@RestrictTo(LIBRARY_GROUP) 43class NotificationCompatBuilder implements NotificationBuilderWithBuilderAccessor { 44 private final Notification.Builder mBuilder; 45 private final NotificationCompat.Builder mBuilderCompat; 46 47 // @RequiresApi(16) - uncomment when lint bug is fixed. 48 private RemoteViews mContentView; 49 // @RequiresApi(16) - uncomment when lint bug is fixed. 50 private RemoteViews mBigContentView; 51 // @RequiresApi(16) - uncomment when lint bug is fixed. 52 private final List<Bundle> mActionExtrasList = new ArrayList<>(); 53 // @RequiresApi(16) - uncomment when lint bug is fixed. 54 private final Bundle mExtras = new Bundle(); 55 // @RequiresApi(20) - uncomment when lint bug is fixed. 56 private int mGroupAlertBehavior; 57 // @RequiresApi(21) - uncomment when lint bug is fixed. 58 private RemoteViews mHeadsUpContentView; 59 60 NotificationCompatBuilder(NotificationCompat.Builder b) { 61 mBuilderCompat = b; 62 if (Build.VERSION.SDK_INT >= 26) { 63 mBuilder = new Notification.Builder(b.mContext, b.mChannelId); 64 } else { 65 mBuilder = new Notification.Builder(b.mContext); 66 } 67 Notification n = b.mNotification; 68 mBuilder.setWhen(n.when) 69 .setSmallIcon(n.icon, n.iconLevel) 70 .setContent(n.contentView) 71 .setTicker(n.tickerText, b.mTickerView) 72 .setSound(n.sound, n.audioStreamType) 73 .setVibrate(n.vibrate) 74 .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS) 75 .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0) 76 .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) 77 .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0) 78 .setDefaults(n.defaults) 79 .setContentTitle(b.mContentTitle) 80 .setContentText(b.mContentText) 81 .setContentInfo(b.mContentInfo) 82 .setContentIntent(b.mContentIntent) 83 .setDeleteIntent(n.deleteIntent) 84 .setFullScreenIntent(b.mFullScreenIntent, 85 (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0) 86 .setLargeIcon(b.mLargeIcon) 87 .setNumber(b.mNumber) 88 .setProgress(b.mProgressMax, b.mProgress, b.mProgressIndeterminate); 89 if (Build.VERSION.SDK_INT >= 16) { 90 mBuilder.setSubText(b.mSubText) 91 .setUsesChronometer(b.mUseChronometer) 92 .setPriority(b.mPriority); 93 94 for (NotificationCompat.Action action : b.mActions) { 95 addAction(action); 96 } 97 98 if (b.mExtras != null) { 99 mExtras.putAll(b.mExtras); 100 } 101 if (Build.VERSION.SDK_INT < 20) { 102 if (b.mLocalOnly) { 103 mExtras.putBoolean(NotificationCompatExtras.EXTRA_LOCAL_ONLY, true); 104 } 105 if (b.mGroupKey != null) { 106 mExtras.putString(NotificationCompatExtras.EXTRA_GROUP_KEY, b.mGroupKey); 107 if (b.mGroupSummary) { 108 mExtras.putBoolean(NotificationCompatExtras.EXTRA_GROUP_SUMMARY, true); 109 } else { 110 mExtras.putBoolean( 111 NotificationManagerCompat.EXTRA_USE_SIDE_CHANNEL, true); 112 } 113 } 114 if (b.mSortKey != null) { 115 mExtras.putString(NotificationCompatExtras.EXTRA_SORT_KEY, b.mSortKey); 116 } 117 } 118 119 mContentView = b.mContentView; 120 mBigContentView = b.mBigContentView; 121 } 122 if (Build.VERSION.SDK_INT >= 19) { 123 mBuilder.setShowWhen(b.mShowWhen); 124 125 if (Build.VERSION.SDK_INT < 21) { 126 if (b.mPeople != null && !b.mPeople.isEmpty()) { 127 mExtras.putStringArray(Notification.EXTRA_PEOPLE, 128 b.mPeople.toArray(new String[b.mPeople.size()])); 129 } 130 } 131 } 132 if (Build.VERSION.SDK_INT >= 20) { 133 mBuilder.setLocalOnly(b.mLocalOnly) 134 .setGroup(b.mGroupKey) 135 .setGroupSummary(b.mGroupSummary) 136 .setSortKey(b.mSortKey); 137 138 mGroupAlertBehavior = b.mGroupAlertBehavior; 139 } 140 if (Build.VERSION.SDK_INT >= 21) { 141 mBuilder.setCategory(b.mCategory) 142 .setColor(b.mColor) 143 .setVisibility(b.mVisibility) 144 .setPublicVersion(b.mPublicVersion); 145 146 for (String person: b.mPeople) { 147 mBuilder.addPerson(person); 148 } 149 mHeadsUpContentView = b.mHeadsUpContentView; 150 } 151 if (Build.VERSION.SDK_INT >= 24) { 152 mBuilder.setExtras(b.mExtras) 153 .setRemoteInputHistory(b.mRemoteInputHistory); 154 if (b.mContentView != null) { 155 mBuilder.setCustomContentView(b.mContentView); 156 } 157 if (b.mBigContentView != null) { 158 mBuilder.setCustomBigContentView(b.mBigContentView); 159 } 160 if (b.mHeadsUpContentView != null) { 161 mBuilder.setCustomHeadsUpContentView(b.mHeadsUpContentView); 162 } 163 } 164 if (Build.VERSION.SDK_INT >= 26) { 165 mBuilder.setBadgeIconType(b.mBadgeIcon) 166 .setShortcutId(b.mShortcutId) 167 .setTimeoutAfter(b.mTimeout) 168 .setGroupAlertBehavior(b.mGroupAlertBehavior); 169 if (b.mColorizedSet) { 170 mBuilder.setColorized(b.mColorized); 171 } 172 } 173 } 174 175 @Override 176 public Notification.Builder getBuilder() { 177 return mBuilder; 178 } 179 180 public Notification build() { 181 final NotificationCompat.Style style = mBuilderCompat.mStyle; 182 if (style != null) { 183 style.apply(this); 184 } 185 186 RemoteViews styleContentView = style != null 187 ? style.makeContentView(this) 188 : null; 189 Notification n = buildInternal(); 190 if (styleContentView != null) { 191 n.contentView = styleContentView; 192 } else if (mBuilderCompat.mContentView != null) { 193 n.contentView = mBuilderCompat.mContentView; 194 } 195 if (Build.VERSION.SDK_INT >= 16 && style != null) { 196 RemoteViews styleBigContentView = style.makeBigContentView(this); 197 if (styleBigContentView != null) { 198 n.bigContentView = styleBigContentView; 199 } 200 } 201 if (Build.VERSION.SDK_INT >= 21 && style != null) { 202 RemoteViews styleHeadsUpContentView = 203 mBuilderCompat.mStyle.makeHeadsUpContentView(this); 204 if (styleHeadsUpContentView != null) { 205 n.headsUpContentView = styleHeadsUpContentView; 206 } 207 } 208 209 if (Build.VERSION.SDK_INT >= 16 && style != null) { 210 Bundle extras = NotificationCompat.getExtras(n); 211 if (extras != null) { 212 style.addCompatExtras(extras); 213 } 214 } 215 216 return n; 217 } 218 219 private void addAction(NotificationCompat.Action action) { 220 if (Build.VERSION.SDK_INT >= 20) { 221 Notification.Action.Builder actionBuilder = new Notification.Action.Builder( 222 action.getIcon(), action.getTitle(), action.getActionIntent()); 223 if (action.getRemoteInputs() != null) { 224 for (android.app.RemoteInput remoteInput : RemoteInput.fromCompat( 225 action.getRemoteInputs())) { 226 actionBuilder.addRemoteInput(remoteInput); 227 } 228 } 229 Bundle actionExtras; 230 if (action.getExtras() != null) { 231 actionExtras = new Bundle(action.getExtras()); 232 } else { 233 actionExtras = new Bundle(); 234 } 235 actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES, 236 action.getAllowGeneratedReplies()); 237 if (Build.VERSION.SDK_INT >= 24) { 238 actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies()); 239 } 240 actionBuilder.addExtras(actionExtras); 241 mBuilder.addAction(actionBuilder.build()); 242 } else if (Build.VERSION.SDK_INT >= 16) { 243 mActionExtrasList.add( 244 NotificationCompatJellybean.writeActionAndGetExtras(mBuilder, action)); 245 } 246 } 247 248 protected Notification buildInternal() { 249 if (Build.VERSION.SDK_INT >= 26) { 250 return mBuilder.build(); 251 } else if (Build.VERSION.SDK_INT >= 24) { 252 Notification notification = mBuilder.build(); 253 254 if (mGroupAlertBehavior != GROUP_ALERT_ALL) { 255 // if is summary and only children should alert 256 if (notification.getGroup() != null 257 && (notification.flags & FLAG_GROUP_SUMMARY) != 0 258 && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) { 259 removeSoundAndVibration(notification); 260 } 261 // if is group child and only summary should alert 262 if (notification.getGroup() != null 263 && (notification.flags & FLAG_GROUP_SUMMARY) == 0 264 && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) { 265 removeSoundAndVibration(notification); 266 } 267 } 268 269 return notification; 270 } else if (Build.VERSION.SDK_INT >= 21) { 271 mBuilder.setExtras(mExtras); 272 Notification notification = mBuilder.build(); 273 if (mContentView != null) { 274 notification.contentView = mContentView; 275 } 276 if (mBigContentView != null) { 277 notification.bigContentView = mBigContentView; 278 } 279 if (mHeadsUpContentView != null) { 280 notification.headsUpContentView = mHeadsUpContentView; 281 } 282 283 if (mGroupAlertBehavior != GROUP_ALERT_ALL) { 284 // if is summary and only children should alert 285 if (notification.getGroup() != null 286 && (notification.flags & FLAG_GROUP_SUMMARY) != 0 287 && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) { 288 removeSoundAndVibration(notification); 289 } 290 // if is group child and only summary should alert 291 if (notification.getGroup() != null 292 && (notification.flags & FLAG_GROUP_SUMMARY) == 0 293 && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) { 294 removeSoundAndVibration(notification); 295 } 296 } 297 return notification; 298 } else if (Build.VERSION.SDK_INT >= 20) { 299 mBuilder.setExtras(mExtras); 300 Notification notification = mBuilder.build(); 301 if (mContentView != null) { 302 notification.contentView = mContentView; 303 } 304 if (mBigContentView != null) { 305 notification.bigContentView = mBigContentView; 306 } 307 308 if (mGroupAlertBehavior != GROUP_ALERT_ALL) { 309 // if is summary and only children should alert 310 if (notification.getGroup() != null 311 && (notification.flags & FLAG_GROUP_SUMMARY) != 0 312 && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) { 313 removeSoundAndVibration(notification); 314 } 315 // if is group child and only summary should alert 316 if (notification.getGroup() != null 317 && (notification.flags & FLAG_GROUP_SUMMARY) == 0 318 && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) { 319 removeSoundAndVibration(notification); 320 } 321 } 322 323 return notification; 324 } else if (Build.VERSION.SDK_INT >= 19) { 325 SparseArray<Bundle> actionExtrasMap = 326 NotificationCompatJellybean.buildActionExtrasMap(mActionExtrasList); 327 if (actionExtrasMap != null) { 328 // Add the action extras sparse array if any action was added with extras. 329 mExtras.putSparseParcelableArray( 330 NotificationCompatExtras.EXTRA_ACTION_EXTRAS, actionExtrasMap); 331 } 332 mBuilder.setExtras(mExtras); 333 Notification notification = mBuilder.build(); 334 if (mContentView != null) { 335 notification.contentView = mContentView; 336 } 337 if (mBigContentView != null) { 338 notification.bigContentView = mBigContentView; 339 } 340 return notification; 341 } else if (Build.VERSION.SDK_INT >= 16) { 342 Notification notification = mBuilder.build(); 343 // Merge in developer provided extras, but let the values already set 344 // for keys take precedence. 345 Bundle extras = NotificationCompat.getExtras(notification); 346 Bundle mergeBundle = new Bundle(mExtras); 347 for (String key : mExtras.keySet()) { 348 if (extras.containsKey(key)) { 349 mergeBundle.remove(key); 350 } 351 } 352 extras.putAll(mergeBundle); 353 SparseArray<Bundle> actionExtrasMap = 354 NotificationCompatJellybean.buildActionExtrasMap(mActionExtrasList); 355 if (actionExtrasMap != null) { 356 // Add the action extras sparse array if any action was added with extras. 357 NotificationCompat.getExtras(notification).putSparseParcelableArray( 358 NotificationCompatExtras.EXTRA_ACTION_EXTRAS, actionExtrasMap); 359 } 360 if (mContentView != null) { 361 notification.contentView = mContentView; 362 } 363 if (mBigContentView != null) { 364 notification.bigContentView = mBigContentView; 365 } 366 return notification; 367 } else { 368 //noinspection deprecation 369 return mBuilder.getNotification(); 370 } 371 } 372 373 private void removeSoundAndVibration(Notification notification) { 374 notification.sound = null; 375 notification.vibrate = null; 376 notification.defaults &= ~DEFAULT_SOUND; 377 notification.defaults &= ~DEFAULT_VIBRATE; 378 } 379} 380