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.internal.app; 18 19import android.app.Notification; 20import android.app.PendingIntent; 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.Bitmap; 24import android.os.Build; 25import android.os.SystemClock; 26import android.support.v7.appcompat.R; 27import android.support.v4.app.NotificationBuilderWithBuilderAccessor; 28import android.support.v4.app.NotificationCompatBase; 29import android.util.TypedValue; 30import android.view.View; 31import android.widget.RemoteViews; 32 33import java.text.NumberFormat; 34import java.util.List; 35 36/** 37 * Helper class to generate MediaStyle notifications for pre-Lollipop platforms. Overrides 38 * contentView and bigContentView of the notification. 39 * @hide 40 */ 41public class NotificationCompatImplBase { 42 43 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 44 static final int MAX_MEDIA_BUTTONS = 5; 45 46 public static <T extends NotificationCompatBase.Action> void overrideContentView( 47 NotificationBuilderWithBuilderAccessor builder, 48 Context context, CharSequence contentTitle, CharSequence contentText, 49 CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText, 50 boolean useChronometer, long when, List<T> actions, int[] actionsToShowInCompact, 51 boolean showCancelButton, PendingIntent cancelButtonIntent) { 52 RemoteViews views = generateContentView(context, contentTitle, contentText, contentInfo, 53 number, largeIcon, subText, useChronometer, when, actions, actionsToShowInCompact, 54 showCancelButton, cancelButtonIntent); 55 builder.getBuilder().setContent(views); 56 if (showCancelButton) { 57 builder.getBuilder().setOngoing(true); 58 } 59 } 60 61 private static <T extends NotificationCompatBase.Action> RemoteViews generateContentView( 62 Context context, CharSequence contentTitle, CharSequence contentText, 63 CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText, 64 boolean useChronometer, long when, List<T> actions, int[] actionsToShowInCompact, 65 boolean showCancelButton, PendingIntent cancelButtonIntent) { 66 RemoteViews view = applyStandardTemplate(context, contentTitle, contentText, contentInfo, 67 number, largeIcon, subText, useChronometer, when, 68 R.layout.notification_template_media, true /* fitIn1U */); 69 70 final int numActions = actions.size(); 71 final int N = actionsToShowInCompact == null 72 ? 0 73 : Math.min(actionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 74 view.removeAllViews(R.id.media_actions); 75 if (N > 0) { 76 for (int i = 0; i < N; i++) { 77 if (i >= numActions) { 78 throw new IllegalArgumentException(String.format( 79 "setShowActionsInCompactView: action %d out of bounds (max %d)", 80 i, numActions - 1)); 81 } 82 83 final NotificationCompatBase.Action action = actions.get(actionsToShowInCompact[i]); 84 final RemoteViews button = generateMediaActionButton(context, action); 85 view.addView(R.id.media_actions, button); 86 } 87 } 88 if (showCancelButton) { 89 view.setViewVisibility(R.id.end_padder, View.GONE); 90 view.setViewVisibility(R.id.cancel_action, View.VISIBLE); 91 view.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent); 92 view.setInt(R.id.cancel_action, "setAlpha", 93 context.getResources().getInteger(R.integer.cancel_button_image_alpha)); 94 } else { 95 view.setViewVisibility(R.id.end_padder, View.VISIBLE); 96 view.setViewVisibility(R.id.cancel_action, View.GONE); 97 } 98 return view; 99 } 100 101 public static <T extends NotificationCompatBase.Action> void overrideBigContentView( 102 Notification n, Context context, CharSequence contentTitle, CharSequence contentText, 103 CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText, 104 boolean useChronometer, long when, List<T> actions, boolean showCancelButton, 105 PendingIntent cancelButtonIntent) { 106 n.bigContentView = generateBigContentView(context, contentTitle, contentText, contentInfo, 107 number, largeIcon, subText, useChronometer, when, actions, showCancelButton, 108 cancelButtonIntent); 109 if (showCancelButton) { 110 n.flags |= Notification.FLAG_ONGOING_EVENT; 111 } 112 } 113 114 private static <T extends NotificationCompatBase.Action> RemoteViews generateBigContentView( 115 Context context, CharSequence contentTitle, CharSequence contentText, 116 CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText, 117 boolean useChronometer, long when, List<T> actions, boolean showCancelButton, 118 PendingIntent cancelButtonIntent) { 119 final int actionCount = Math.min(actions.size(), MAX_MEDIA_BUTTONS); 120 RemoteViews big = applyStandardTemplate(context, contentTitle, contentText, contentInfo, 121 number, largeIcon, subText, useChronometer, when, 122 getBigLayoutResource(actionCount), false /* fitIn1U */); 123 124 big.removeAllViews(R.id.media_actions); 125 if (actionCount > 0) { 126 for (int i = 0; i < actionCount; i++) { 127 final RemoteViews button = generateMediaActionButton(context, actions.get(i)); 128 big.addView(R.id.media_actions, button); 129 } 130 } 131 if (showCancelButton) { 132 big.setViewVisibility(R.id.cancel_action, View.VISIBLE); 133 big.setInt(R.id.cancel_action, "setAlpha", 134 context.getResources().getInteger(R.integer.cancel_button_image_alpha)); 135 big.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent); 136 } else { 137 big.setViewVisibility(R.id.cancel_action, View.GONE); 138 } 139 return big; 140 } 141 142 private static RemoteViews generateMediaActionButton(Context context, 143 NotificationCompatBase.Action action) { 144 final boolean tombstone = (action.getActionIntent() == null); 145 RemoteViews button = new RemoteViews(context.getPackageName(), 146 R.layout.notification_media_action); 147 button.setImageViewResource(R.id.action0, action.getIcon()); 148 if (!tombstone) { 149 button.setOnClickPendingIntent(R.id.action0, action.getActionIntent()); 150 } 151 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 152 button.setContentDescription(R.id.action0, action.getTitle()); 153 } 154 return button; 155 } 156 157 private static int getBigLayoutResource(int actionCount) { 158 if (actionCount <= 3) { 159 return R.layout.notification_template_big_media_narrow; 160 } else { 161 return R.layout.notification_template_big_media; 162 } 163 } 164 165 private static RemoteViews applyStandardTemplate(Context context, 166 CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo, 167 int number, Bitmap largeIcon, CharSequence subText, boolean useChronometer, long when, 168 int resId, boolean fitIn1U) { 169 RemoteViews contentView = new RemoteViews(context.getPackageName(), resId); 170 boolean showLine3 = false; 171 boolean showLine2 = false; 172 173 // On versions before Jellybean, the large icon was shown by SystemUI, so we need to hide 174 // it here. 175 if (largeIcon != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 176 contentView.setImageViewBitmap(R.id.icon, largeIcon); 177 } else { 178 contentView.setViewVisibility(R.id.icon, View.GONE); 179 } 180 if (contentTitle != null) { 181 contentView.setTextViewText(R.id.title, contentTitle); 182 } 183 if (contentText != null) { 184 contentView.setTextViewText(R.id.text, contentText); 185 showLine3 = true; 186 } 187 if (contentInfo != null) { 188 contentView.setTextViewText(R.id.info, contentInfo); 189 contentView.setViewVisibility(R.id.info, View.VISIBLE); 190 showLine3 = true; 191 } else if (number > 0) { 192 final int tooBig = context.getResources().getInteger( 193 R.integer.status_bar_notification_info_maxnum); 194 if (number > tooBig) { 195 contentView.setTextViewText(R.id.info, context.getResources().getString( 196 R.string.status_bar_notification_info_overflow)); 197 } else { 198 NumberFormat f = NumberFormat.getIntegerInstance(); 199 contentView.setTextViewText(R.id.info, f.format(number)); 200 } 201 contentView.setViewVisibility(R.id.info, View.VISIBLE); 202 showLine3 = true; 203 } else { 204 contentView.setViewVisibility(R.id.info, View.GONE); 205 } 206 207 // Need to show three lines? Only allow on Jellybean+ 208 if (subText != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 209 contentView.setTextViewText(R.id.text, subText); 210 if (contentText != null) { 211 contentView.setTextViewText(R.id.text2, contentText); 212 contentView.setViewVisibility(R.id.text2, View.VISIBLE); 213 showLine2 = true; 214 } else { 215 contentView.setViewVisibility(R.id.text2, View.GONE); 216 } 217 } 218 219 // RemoteViews.setViewPadding and RemoteViews.setTextViewTextSize is not available on ICS- 220 if (showLine2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 221 if (fitIn1U) { 222 // need to shrink all the type to make sure everything fits 223 final Resources res = context.getResources(); 224 final float subTextSize = res.getDimensionPixelSize( 225 R.dimen.notification_subtext_size); 226 contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); 227 } 228 // vertical centering 229 contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); 230 } 231 232 if (when != 0) { 233 if (useChronometer) { 234 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 235 contentView.setLong(R.id.chronometer, "setBase", 236 when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 237 contentView.setBoolean(R.id.chronometer, "setStarted", true); 238 } else { 239 contentView.setViewVisibility(R.id.time, View.VISIBLE); 240 contentView.setLong(R.id.time, "setTime", when); 241 } 242 } 243 contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); 244 return contentView; 245 } 246} 247