1 /* 2 * Copyright (C) 2020 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 17 package com.android.settings.notification.history; 18 19 import static android.app.Notification.COLOR_DEFAULT; 20 import static android.content.pm.PackageManager.MATCH_ANY_USER; 21 import static android.content.pm.PackageManager.NameNotFoundException; 22 import static android.os.UserHandle.USER_ALL; 23 import static android.provider.Settings.EXTRA_APP_PACKAGE; 24 import static android.provider.Settings.EXTRA_CHANNEL_ID; 25 import static android.provider.Settings.EXTRA_CONVERSATION_ID; 26 27 import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_BOTTOM; 28 import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_CENTER; 29 import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_TOP; 30 31 import android.annotation.ColorInt; 32 import android.annotation.UserIdInt; 33 import android.app.ActivityManager; 34 import android.app.Notification; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.content.res.Configuration; 40 import android.graphics.PorterDuff; 41 import android.graphics.PorterDuffColorFilter; 42 import android.graphics.drawable.Drawable; 43 import android.os.UserHandle; 44 import android.os.UserManager; 45 import android.provider.Settings; 46 import android.service.notification.StatusBarNotification; 47 import android.text.TextUtils; 48 import android.util.Log; 49 import android.util.Slog; 50 import android.view.LayoutInflater; 51 import android.view.View; 52 import android.view.ViewGroup; 53 54 import androidx.annotation.NonNull; 55 import androidx.recyclerview.widget.RecyclerView; 56 57 import com.android.internal.logging.UiEventLogger; 58 import com.android.internal.util.ContrastColorUtil; 59 import com.android.settings.R; 60 import com.android.settingslib.Utils; 61 62 import java.util.ArrayList; 63 import java.util.HashMap; 64 import java.util.List; 65 import java.util.Map; 66 67 public class NotificationSbnAdapter extends 68 RecyclerView.Adapter<NotificationSbnViewHolder> { 69 70 private static final String TAG = "SbnAdapter"; 71 private List<StatusBarNotification> mValues; 72 private Map<Integer, Drawable> mUserBadgeCache; 73 private final Context mContext; 74 private PackageManager mPm; 75 private @ColorInt int mBackgroundColor; 76 private boolean mInNightMode; 77 private @UserIdInt int mCurrentUser; 78 private List<Integer> mEnabledProfiles = new ArrayList<>(); 79 private boolean mIsSnoozed; 80 private UiEventLogger mUiEventLogger; 81 NotificationSbnAdapter(Context context, PackageManager pm, UserManager um, boolean isSnoozed, UiEventLogger uiEventLogger)82 public NotificationSbnAdapter(Context context, PackageManager pm, UserManager um, 83 boolean isSnoozed, UiEventLogger uiEventLogger) { 84 mContext = context; 85 mPm = pm; 86 mUserBadgeCache = new HashMap<>(); 87 mValues = new ArrayList<>(); 88 mBackgroundColor = Utils.getColorAttrDefaultColor(context, 89 android.R.attr.colorBackground); 90 Configuration currentConfig = mContext.getResources().getConfiguration(); 91 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 92 == Configuration.UI_MODE_NIGHT_YES; 93 mCurrentUser = ActivityManager.getCurrentUser(); 94 int[] enabledUsers = um.getEnabledProfileIds(mCurrentUser); 95 for (int id : enabledUsers) { 96 if (!um.isQuietModeEnabled(UserHandle.of(id))) { 97 mEnabledProfiles.add(id); 98 } 99 } 100 setHasStableIds(true); 101 // If true, this is the panel for snoozed notifs, otherwise the one for dismissed notifs. 102 mIsSnoozed = isSnoozed; 103 mUiEventLogger = uiEventLogger; 104 } 105 106 @Override onCreateViewHolder(ViewGroup parent, int viewType)107 public NotificationSbnViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 108 View view = LayoutInflater.from(parent.getContext()) 109 .inflate(R.layout.notification_sbn_log_row, parent, false); 110 return new NotificationSbnViewHolder(view); 111 } 112 113 @Override onBindViewHolder(final @NonNull NotificationSbnViewHolder holder, int position)114 public void onBindViewHolder(final @NonNull NotificationSbnViewHolder holder, 115 int position) { 116 final StatusBarNotification sbn = mValues.get(position); 117 if (sbn != null) { 118 int cornerType = ROUND_CORNER_CENTER; 119 if (position == (getItemCount() - 1)) { 120 cornerType |= ROUND_CORNER_BOTTOM; 121 } 122 if (position == 0) { 123 cornerType |= ROUND_CORNER_TOP; 124 } 125 int backgroundRes = NotificationHistoryActivity.getRoundCornerDrawableRes(cornerType); 126 holder.itemView.setBackgroundResource(backgroundRes); 127 128 holder.setIconBackground(loadBackground(sbn)); 129 holder.setIcon(loadIcon(sbn)); 130 holder.setPackageLabel(loadPackageLabel(sbn.getPackageName()).toString()); 131 holder.setTitle(getTitleString(sbn.getNotification())); 132 holder.setSummary(getTextString(mContext, sbn.getNotification())); 133 holder.setPostedTime(sbn.getPostTime()); 134 int userId = normalizeUserId(sbn); 135 if (!mUserBadgeCache.containsKey(userId)) { 136 Drawable profile = mContext.getPackageManager().getUserBadgeForDensityNoBackground( 137 UserHandle.of(userId), 0); 138 mUserBadgeCache.put(userId, profile); 139 } 140 holder.setProfileBadge(mUserBadgeCache.get(userId)); 141 holder.addOnClick(position, sbn.getPackageName(), sbn.getUid(), sbn.getUserId(), 142 sbn.getNotification().contentIntent, sbn.getInstanceId(), mIsSnoozed, 143 mUiEventLogger); 144 holder.itemView.setOnLongClickListener(v -> { 145 Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 146 .setPackage(mContext.getPackageName()) 147 .putExtra(EXTRA_APP_PACKAGE, sbn.getPackageName()) 148 .putExtra(EXTRA_CHANNEL_ID, sbn.getNotification().getChannelId()) 149 .putExtra(EXTRA_CONVERSATION_ID, sbn.getNotification().getShortcutId()); 150 holder.itemView.getContext().startActivityAsUser(intent, UserHandle.of(userId)); 151 return true; 152 }); 153 } else { 154 Slog.w(TAG, "null entry in list at position " + position); 155 } 156 } 157 loadBackground(StatusBarNotification sbn)158 private Drawable loadBackground(StatusBarNotification sbn) { 159 Drawable bg = mContext.getDrawable(R.drawable.circle); 160 int color = sbn.getNotification().color; 161 if (color == COLOR_DEFAULT) { 162 color = Utils.getColorAttrDefaultColor( 163 mContext, com.android.internal.R.attr.colorAccent); 164 } 165 color = ContrastColorUtil.resolveContrastColor( 166 mContext, color, mBackgroundColor, mInNightMode); 167 bg.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); 168 return bg; 169 } 170 171 @Override getItemCount()172 public int getItemCount() { 173 return mValues.size(); 174 } 175 onRebuildComplete(List<StatusBarNotification> notifications)176 public void onRebuildComplete(List<StatusBarNotification> notifications) { 177 for (int i = notifications.size() - 1; i >= 0; i--) { 178 StatusBarNotification sbn = notifications.get(i); 179 if (!shouldShowSbn(sbn)) { 180 notifications.remove(i); 181 } 182 } 183 mValues = notifications; 184 notifyDataSetChanged(); 185 } 186 addSbn(StatusBarNotification sbn)187 public void addSbn(StatusBarNotification sbn) { 188 if (!shouldShowSbn(sbn)) { 189 return; 190 } 191 mValues.add(0, sbn); 192 notifyDataSetChanged(); 193 } 194 shouldShowSbn(StatusBarNotification sbn)195 private boolean shouldShowSbn(StatusBarNotification sbn) { 196 // summaries are low content; don't bother showing them 197 if (sbn.isGroup() && sbn.getNotification().isGroupSummary()) { 198 return false; 199 } 200 // also don't show profile notifications if the profile is currently disabled 201 if (!mEnabledProfiles.contains(normalizeUserId(sbn))) { 202 return false; 203 } 204 return true; 205 } 206 loadPackageLabel(String pkg)207 private @NonNull CharSequence loadPackageLabel(String pkg) { 208 try { 209 ApplicationInfo info = mPm.getApplicationInfo(pkg, 210 MATCH_ANY_USER); 211 if (info != null) return mPm.getApplicationLabel(info); 212 } catch (NameNotFoundException e) { 213 Log.e(TAG, "Cannot load package name", e); 214 } 215 return pkg; 216 } 217 getTitleString(Notification n)218 private static String getTitleString(Notification n) { 219 CharSequence title = null; 220 if (n.extras != null) { 221 title = n.extras.getCharSequence(Notification.EXTRA_TITLE); 222 } 223 return title == null? null : String.valueOf(title); 224 } 225 226 /** 227 * Returns the appropriate substring for this notification based on the style of notification. 228 */ getTextString(Context appContext, Notification n)229 private static String getTextString(Context appContext, Notification n) { 230 CharSequence text = null; 231 if (n.extras != null) { 232 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 233 234 Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n); 235 236 if (nb.getStyle() instanceof Notification.BigTextStyle) { 237 text = ((Notification.BigTextStyle) nb.getStyle()).getBigText(); 238 } else if (nb.getStyle() instanceof Notification.MessagingStyle) { 239 Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle(); 240 final List<Notification.MessagingStyle.Message> messages = ms.getMessages(); 241 if (messages != null && messages.size() > 0) { 242 text = messages.get(messages.size() - 1).getText(); 243 } 244 } 245 246 if (TextUtils.isEmpty(text)) { 247 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 248 } 249 } 250 return text == null ? null : String.valueOf(text); 251 } 252 loadIcon(StatusBarNotification sbn)253 private Drawable loadIcon(StatusBarNotification sbn) { 254 Drawable draw = sbn.getNotification().getSmallIcon().loadDrawableAsUser( 255 sbn.getPackageContext(mContext), normalizeUserId(sbn)); 256 if (draw == null) { 257 return null; 258 } 259 draw.mutate(); 260 draw.setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_ATOP); 261 return draw; 262 } 263 normalizeUserId(StatusBarNotification sbn)264 private int normalizeUserId(StatusBarNotification sbn) { 265 int userId = sbn.getUserId(); 266 if (userId == USER_ALL) { 267 userId = mCurrentUser; 268 } 269 return userId; 270 } 271 } 272