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