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