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().getUserBadgeForDensityNoBackground( 124 UserHandle.of(userId), 0); 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 .setPackage(mContext.getPackageName()) 134 .putExtra(EXTRA_APP_PACKAGE, sbn.getPackageName()) 135 .putExtra(EXTRA_CHANNEL_ID, sbn.getNotification().getChannelId()) 136 .putExtra(EXTRA_CONVERSATION_ID, sbn.getNotification().getShortcutId()); 137 holder.itemView.getContext().startActivityAsUser(intent, UserHandle.of(userId)); 138 return true; 139 }); 140 } else { 141 Slog.w(TAG, "null entry in list at position " + position); 142 } 143 } 144 loadBackground(StatusBarNotification sbn)145 private Drawable loadBackground(StatusBarNotification sbn) { 146 Drawable bg = mContext.getDrawable(R.drawable.circle); 147 int color = sbn.getNotification().color; 148 if (color == COLOR_DEFAULT) { 149 color = Utils.getColorAttrDefaultColor( 150 mContext, com.android.internal.R.attr.colorAccent); 151 } 152 color = ContrastColorUtil.resolveContrastColor( 153 mContext, color, mBackgroundColor, mInNightMode); 154 bg.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); 155 return bg; 156 } 157 158 @Override getItemCount()159 public int getItemCount() { 160 return mValues.size(); 161 } 162 onRebuildComplete(List<StatusBarNotification> notifications)163 public void onRebuildComplete(List<StatusBarNotification> notifications) { 164 for (int i = notifications.size() - 1; i >= 0; i--) { 165 StatusBarNotification sbn = notifications.get(i); 166 if (!shouldShowSbn(sbn)) { 167 notifications.remove(i); 168 } 169 } 170 mValues = notifications; 171 notifyDataSetChanged(); 172 } 173 addSbn(StatusBarNotification sbn)174 public void addSbn(StatusBarNotification sbn) { 175 if (!shouldShowSbn(sbn)) { 176 return; 177 } 178 mValues.add(0, sbn); 179 notifyDataSetChanged(); 180 } 181 shouldShowSbn(StatusBarNotification sbn)182 private boolean shouldShowSbn(StatusBarNotification sbn) { 183 // summaries are low content; don't bother showing them 184 if (sbn.isGroup() && sbn.getNotification().isGroupSummary()) { 185 return false; 186 } 187 // also don't show profile notifications if the profile is currently disabled 188 if (!mEnabledProfiles.contains(normalizeUserId(sbn))) { 189 return false; 190 } 191 return true; 192 } 193 loadPackageLabel(String pkg)194 private @NonNull CharSequence loadPackageLabel(String pkg) { 195 try { 196 ApplicationInfo info = mPm.getApplicationInfo(pkg, 197 MATCH_ANY_USER); 198 if (info != null) return mPm.getApplicationLabel(info); 199 } catch (NameNotFoundException e) { 200 Log.e(TAG, "Cannot load package name", e); 201 } 202 return pkg; 203 } 204 getTitleString(Notification n)205 private static String getTitleString(Notification n) { 206 CharSequence title = null; 207 if (n.extras != null) { 208 title = n.extras.getCharSequence(Notification.EXTRA_TITLE); 209 } 210 return title == null? null : String.valueOf(title); 211 } 212 213 /** 214 * Returns the appropriate substring for this notification based on the style of notification. 215 */ getTextString(Context appContext, Notification n)216 private static String getTextString(Context appContext, Notification n) { 217 CharSequence text = null; 218 if (n.extras != null) { 219 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 220 221 Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n); 222 223 if (nb.getStyle() instanceof Notification.BigTextStyle) { 224 text = ((Notification.BigTextStyle) nb.getStyle()).getBigText(); 225 } else if (nb.getStyle() instanceof Notification.MessagingStyle) { 226 Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle(); 227 final List<Notification.MessagingStyle.Message> messages = ms.getMessages(); 228 if (messages != null && messages.size() > 0) { 229 text = messages.get(messages.size() - 1).getText(); 230 } 231 } 232 233 if (TextUtils.isEmpty(text)) { 234 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 235 } 236 } 237 return text == null ? null : String.valueOf(text); 238 } 239 loadIcon(StatusBarNotification sbn)240 private Drawable loadIcon(StatusBarNotification sbn) { 241 Drawable draw = sbn.getNotification().getSmallIcon().loadDrawableAsUser( 242 sbn.getPackageContext(mContext), normalizeUserId(sbn)); 243 if (draw == null) { 244 return null; 245 } 246 draw.mutate(); 247 draw.setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_ATOP); 248 return draw; 249 } 250 normalizeUserId(StatusBarNotification sbn)251 private int normalizeUserId(StatusBarNotification sbn) { 252 int userId = sbn.getUserId(); 253 if (userId == USER_ALL) { 254 userId = mCurrentUser; 255 } 256 return userId; 257 } 258 } 259