diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5069110ba7b..1f9c6504894 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1193,6 +1193,15 @@
android:value="com.android.settings.notification.history.NotificationStation" />
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_clear.xml b/res/drawable/ic_clear.xml
new file mode 100644
index 00000000000..224425fa27d
--- /dev/null
+++ b/res/drawable/ic_clear.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_snooze.xml b/res/drawable/ic_snooze.xml
new file mode 100644
index 00000000000..d7bbbaf42a7
--- /dev/null
+++ b/res/drawable/ic_snooze.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/res/drawable/ic_today.xml b/res/drawable/ic_today.xml
new file mode 100644
index 00000000000..1d396104ce8
--- /dev/null
+++ b/res/drawable/ic_today.xml
@@ -0,0 +1,24 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/rounded_bg.xml b/res/drawable/rounded_bg.xml
new file mode 100644
index 00000000000..fcdd7614e64
--- /dev/null
+++ b/res/drawable/rounded_bg.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/notification_history.xml b/res/layout/notification_history.xml
new file mode 100644
index 00000000000..91a59a8e8d0
--- /dev/null
+++ b/res/layout/notification_history.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/notification_history_app_layout.xml b/res/layout/notification_history_app_layout.xml
new file mode 100644
index 00000000000..6f26789e947
--- /dev/null
+++ b/res/layout/notification_history_app_layout.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/notification_history_log_row.xml b/res/layout/notification_history_log_row.xml
new file mode 100644
index 00000000000..4c38167faf0
--- /dev/null
+++ b/res/layout/notification_history_log_row.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/notification_sbn_log_row.xml b/res/layout/notification_sbn_log_row.xml
new file mode 100644
index 00000000000..3c51abd8e6f
--- /dev/null
+++ b/res/layout/notification_sbn_log_row.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 28f7a1956f4..f79874c216d 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -81,6 +81,8 @@
16sp
14dp
+ 26dp
+
16dp
7dp
17dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 871aa82ae59..35a76a45e1f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6163,6 +6163,11 @@
More details
Notification log
+ Notification history
+ Today
+ Snoozed
+ Recently dismissed
+
Call ringtone & vibrate
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index a8396a14929..f749764a162 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -21,6 +21,7 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.NotificationHistory;
import android.app.role.RoleManager;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
@@ -367,6 +368,15 @@ public class NotificationBackend {
return false;
}
+ public NotificationHistory getNotificationHistory(String pkg) {
+ try {
+ return sINM.getNotificationHistory(pkg);
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ }
+ return new NotificationHistory();
+ }
+
protected void recordAggregatedUsageEvents(Context context, AppRow appRow) {
long now = System.currentTimeMillis();
long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
diff --git a/src/com/android/settings/notification/history/HistoryLoader.java b/src/com/android/settings/notification/history/HistoryLoader.java
new file mode 100644
index 00000000000..046b5efeb4d
--- /dev/null
+++ b/src/com/android/settings/notification/history/HistoryLoader.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HistoryLoader {
+ private final Context mContext;
+ private final NotificationBackend mBackend;
+ private final PackageManager mPm;
+
+ public HistoryLoader(Context context, NotificationBackend backend, PackageManager pm) {
+ mContext = context;
+ mBackend = backend;
+ mPm = pm;
+ }
+
+ public void load(OnHistoryLoaderListener listener) {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Map historicalNotifications = new HashMap<>();
+ NotificationHistory history =
+ mBackend.getNotificationHistory(mContext.getPackageName());
+
+ while (history.hasNextNotification()) {
+ HistoricalNotification hn = history.getNextNotification();
+
+ String key = hn.getPackage() + "|" + hn.getUid();
+ NotificationHistoryPackage hnsForPackage = historicalNotifications.getOrDefault(
+ key,
+ new NotificationHistoryPackage(hn.getPackage(), hn.getUid()));
+ hnsForPackage.notifications.add(hn);
+ historicalNotifications.put(key, hnsForPackage);
+ }
+ List packages =
+ new ArrayList<>(historicalNotifications.values());
+ Collections.sort(packages,
+ (o1, o2) -> -1 * Long.compare(o1.getMostRecent(), o2.getMostRecent()));
+ for (NotificationHistoryPackage nhp : packages) {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfoAsUser(
+ nhp.pkgName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE,
+ UserHandle.getUserId(nhp.uid));
+ if (info != null) {
+ nhp.label = String.valueOf(mPm.getApplicationLabel(info));
+ nhp.icon = mPm.getUserBadgedIcon(mPm.getApplicationIcon(info),
+ UserHandle.of(UserHandle.getUserId(nhp.uid)));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // app is gone, just show package name and generic icon
+ nhp.icon = mPm.getDefaultActivityIcon();
+ }
+ }
+ ThreadUtils.postOnMainThread(() -> listener.onHistoryLoaded(packages));
+ });
+ }
+
+ interface OnHistoryLoaderListener {
+ void onHistoryLoaded(List notificationsByPackage);
+ }
+}
diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
new file mode 100644
index 00000000000..173508b4bec
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.INotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
+
+import java.util.Arrays;
+
+public class NotificationHistoryActivity extends Activity {
+
+ private static String TAG = "NotifHistory";
+
+ private ViewGroup mTodayView;
+ private ViewGroup mSnoozeView;
+ private ViewGroup mDismissView;
+ private HistoryLoader mHistoryLoader;
+ private INotificationManager mNm;
+ private PackageManager mPm;
+
+ private HistoryLoader.OnHistoryLoaderListener mOnHistoryLoaderListener = notifications -> {
+ // for each package, new header and recycler view
+ for (NotificationHistoryPackage nhp : notifications) {
+ View viewForPackage = LayoutInflater.from(this)
+ .inflate(R.layout.notification_history_app_layout, null);
+
+ final View container = viewForPackage.findViewById(R.id.list_container);
+ container.setVisibility(View.GONE);
+ ImageButton expand = viewForPackage.findViewById(R.id.expand);
+ expand.setOnClickListener(v -> {
+ container.setVisibility(container.getVisibility() == View.VISIBLE
+ ? View.GONE : View.VISIBLE);
+ expand.setImageResource(container.getVisibility() == View.VISIBLE
+ ? R.drawable.ic_expand_less
+ : com.android.internal.R.drawable.ic_expand_more);
+ });
+
+ TextView label = viewForPackage.findViewById(R.id.label);
+ label.setText(nhp.label != null ? nhp.label : nhp.pkgName);
+ ImageView icon = viewForPackage.findViewById(R.id.icon);
+ icon.setImageDrawable(nhp.icon);
+
+ RecyclerView rv = viewForPackage.findViewById(R.id.notification_list);
+ rv.setLayoutManager(new LinearLayoutManager(this));
+ rv.setAdapter(new NotificationHistoryAdapter());
+ ((NotificationHistoryAdapter) rv.getAdapter()).onRebuildComplete(nhp.notifications);
+ mTodayView.addView(viewForPackage);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notification_history);
+ mTodayView = findViewById(R.id.apps);
+ mSnoozeView = findViewById(R.id.snoozed_list);
+ mDismissView = findViewById(R.id.recently_dismissed_list);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mPm = getPackageManager();
+
+ mHistoryLoader = new HistoryLoader(this, new NotificationBackend(), mPm);
+ mHistoryLoader.load(mOnHistoryLoaderListener);
+
+ mNm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ mListener.registerAsSystemService(this, new ComponentName(getPackageName(),
+ this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot register listener", e);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ try {
+ mListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot unregister listener", e);
+ }
+ super.onPause();
+ }
+
+ private final NotificationListenerService mListener = new NotificationListenerService() {
+
+ @Override
+ public void onListenerConnected() {
+ StatusBarNotification[] snoozed = getSnoozedNotifications();
+ if (snoozed == null || snoozed.length == 0) {
+ mSnoozeView.setVisibility(View.GONE);
+ } else {
+ RecyclerView rv = mSnoozeView.findViewById(R.id.notification_list);
+ rv.setLayoutManager(new LinearLayoutManager(NotificationHistoryActivity.this));
+ rv.setAdapter(new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm));
+ ((NotificationSbnAdapter) rv.getAdapter()).onRebuildComplete(
+ Arrays.asList(snoozed));
+ }
+
+ try {
+ StatusBarNotification[] dismissed = mNm.getHistoricalNotifications(
+ NotificationHistoryActivity.this.getPackageName(), 10);
+ RecyclerView rv = mDismissView.findViewById(R.id.notification_list);
+ rv.setLayoutManager(new LinearLayoutManager(NotificationHistoryActivity.this));
+ rv.setAdapter(new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm));
+ ((NotificationSbnAdapter) rv.getAdapter()).onRebuildComplete(
+ Arrays.asList(dismissed));
+ mDismissView.setVisibility(View.VISIBLE);
+ } catch (Exception e) {
+ Slog.e(TAG, "Cannot load recently dismissed", e);
+ mDismissView.setVisibility(View.GONE);
+ }
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/history/NotificationHistoryAdapter.java b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java
new file mode 100644
index 00000000000..f87bb20373e
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class NotificationHistoryAdapter extends
+ RecyclerView.Adapter {
+
+ private List mValues;
+
+ public NotificationHistoryAdapter() {
+ mValues = new ArrayList<>();
+ setHasStableIds(true);
+ }
+
+ @Override
+ public NotificationHistoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.notification_history_log_row, parent, false);
+ return new NotificationHistoryViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final @NonNull NotificationHistoryViewHolder holder,
+ int position) {
+ final HistoricalNotification hn = mValues.get(position);
+ holder.setTitle(hn.getTitle());
+ holder.setSummary(hn.getText());
+ holder.setPostedTime(hn.getPostedTimeMs());
+ holder.addOnClick(hn.getPackage(), hn.getUserId(), hn.getChannelId());
+ }
+
+ @Override
+ public int getItemCount() {
+ return mValues.size();
+ }
+
+ public void onRebuildComplete(List notifications) {
+ mValues = notifications;
+ notifyDataSetChanged();
+ }
+}
diff --git a/src/com/android/settings/notification/history/NotificationHistoryPackage.java b/src/com/android/settings/notification/history/NotificationHistoryPackage.java
new file mode 100644
index 00000000000..5170b75d7fd
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationHistoryPackage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import android.app.NotificationHistory;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class NotificationHistoryPackage {
+ String pkgName;
+ int uid;
+ List notifications;
+ CharSequence label;
+ Drawable icon;
+
+ public NotificationHistoryPackage(String pkgName, int uid) {
+ this.pkgName = pkgName;
+ this.uid = uid;
+ notifications = new ArrayList<>();
+ }
+
+ public long getMostRecent() {
+ if (notifications.isEmpty()) {
+ return 0;
+ }
+ return notifications.get(0).getPostedTimeMs();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NotificationHistoryPackage that = (NotificationHistoryPackage) o;
+ return uid == that.uid &&
+ Objects.equals(pkgName, that.pkgName) &&
+ Objects.equals(notifications, that.notifications) &&
+ Objects.equals(label, that.label) &&
+ Objects.equals(icon, that.icon);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(pkgName, uid, notifications, label, icon);
+ }
+}
diff --git a/src/com/android/settings/notification/history/NotificationHistoryViewHolder.java b/src/com/android/settings/notification/history/NotificationHistoryViewHolder.java
new file mode 100644
index 00000000000..35f5615f046
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationHistoryViewHolder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import static android.provider.Settings.EXTRA_APP_PACKAGE;
+import static android.provider.Settings.EXTRA_CHANNEL_ID;
+
+import android.content.Intent;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.DateTimeView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+
+public class NotificationHistoryViewHolder extends RecyclerView.ViewHolder {
+
+ private final DateTimeView mTime;
+ private final TextView mTitle;
+ private final TextView mSummary;
+
+ NotificationHistoryViewHolder(View itemView) {
+ super(itemView);
+ mTime = itemView.findViewById(R.id.timestamp);
+ mTime.setShowRelativeTime(true);
+ mTitle = itemView.findViewById(R.id.title);
+ mSummary = itemView.findViewById(R.id.text);
+ }
+
+ void setSummary(CharSequence summary) {
+ mSummary.setText(summary);
+ mSummary.setVisibility(summary != null ? View.VISIBLE : View.GONE);
+ }
+
+ void setTitle(CharSequence title) {
+ mTitle.setText(title);
+ mTitle.setVisibility(title != null ? View.VISIBLE : View.GONE);
+ }
+
+ void setPostedTime(long postedTime) {
+ mTime.setTime(postedTime);
+ }
+
+ void addOnClick(String pkg, int userId, String channelId) {
+ itemView.setOnClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+ .putExtra(EXTRA_APP_PACKAGE, pkg)
+ .putExtra(EXTRA_CHANNEL_ID, channelId);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ itemView.getContext().startActivityAsUser(intent, UserHandle.of(userId));
+ });
+ }
+}
diff --git a/src/com/android/settings/notification/history/NotificationSbnAdapter.java b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
new file mode 100644
index 00000000000..80ba278e257
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import static android.content.pm.PackageManager.*;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NotificationSbnAdapter extends
+ RecyclerView.Adapter {
+
+ private static final String TAG = "SbnAdapter";
+ private List mValues;
+ private Map mUserBadgeCache;
+ private final Context mContext;
+ private PackageManager mPm;
+
+ public NotificationSbnAdapter(Context context, PackageManager pm) {
+ mContext = context;
+ mPm = pm;
+ mUserBadgeCache = new HashMap<>();
+ mValues = new ArrayList<>();
+ setHasStableIds(true);
+ }
+
+ @Override
+ public NotificationSbnViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.notification_sbn_log_row, parent, false);
+ return new NotificationSbnViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final @NonNull NotificationSbnViewHolder holder,
+ int position) {
+ final StatusBarNotification sbn = mValues.get(position);
+ holder.setIcon(loadIcon(sbn));
+ holder.setPackageName(loadPackageName(sbn.getPackageName()).toString());
+ holder.setTitle(getTitleString(sbn.getNotification()));
+ holder.setSummary(getTextString(mContext, sbn.getNotification()));
+ holder.setPostedTime(sbn.getPostTime());
+ if (!mUserBadgeCache.containsKey(sbn.getUserId())) {
+ Drawable profile = mContext.getPackageManager().getUserBadgeForDensity(
+ UserHandle.of(sbn.getUserId()), -1);
+ mUserBadgeCache.put(sbn.getUserId(), profile);
+ }
+ holder.setProfileBadge(mUserBadgeCache.get(sbn.getUserId()));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mValues.size();
+ }
+
+ public void onRebuildComplete(List notifications) {
+ mValues = notifications;
+ notifyDataSetChanged();
+ }
+
+ private @NonNull CharSequence loadPackageName(String pkg) {
+ try {
+ ApplicationInfo info = mPm.getApplicationInfo(pkg,
+ MATCH_ANY_USER);
+ if (info != null) return mPm.getApplicationLabel(info);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Cannot load package name", e);
+ }
+ return pkg;
+ }
+
+ private static String getTitleString(Notification n) {
+ CharSequence title = null;
+ if (n.extras != null) {
+ title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
+ }
+ return title == null? null : String.valueOf(title);
+ }
+
+ /**
+ * Returns the appropriate substring for this notification based on the style of notification.
+ */
+ private static String getTextString(Context appContext, Notification n) {
+ CharSequence text = null;
+ if (n.extras != null) {
+ text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
+
+ Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n);
+
+ if (nb.getStyle() instanceof Notification.BigTextStyle) {
+ text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
+ } else if (nb.getStyle() instanceof Notification.MessagingStyle) {
+ Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
+ final List messages = ms.getMessages();
+ if (messages != null && messages.size() > 0) {
+ text = messages.get(messages.size() - 1).getText();
+ }
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
+ }
+ }
+ return text == null ? null : String.valueOf(text);
+ }
+
+ private Drawable loadIcon(StatusBarNotification sbn) {
+ Drawable draw = sbn.getNotification().getSmallIcon().loadDrawableAsUser(
+ sbn.getPackageContext(mContext), sbn.getUserId());
+ if (draw == null) {
+ return null;
+ }
+ draw.mutate();
+ draw.setColorFilter(sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
+ return draw;
+ }
+}
diff --git a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
new file mode 100644
index 00000000000..79cda7fe267
--- /dev/null
+++ b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.history;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.DateTimeView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+
+public class NotificationSbnViewHolder extends RecyclerView.ViewHolder {
+
+ private final TextView mPkgName;
+ private final ImageView mIcon;
+ private final DateTimeView mTime;
+ private final TextView mTitle;
+ private final TextView mSummary;
+ private final ImageView mProfileBadge;
+
+ NotificationSbnViewHolder(View itemView) {
+ super(itemView);
+ mPkgName = itemView.findViewById(R.id.pkgname);
+ mIcon = itemView.findViewById(R.id.icon);
+ mTime = itemView.findViewById(R.id.timestamp);
+ mTitle = itemView.findViewById(R.id.title);
+ mSummary = itemView.findViewById(R.id.text);
+ mProfileBadge = itemView.findViewById(R.id.profile_badge);
+ }
+
+ void setSummary(CharSequence summary) {
+ mSummary.setVisibility(TextUtils.isEmpty(summary) ? View.GONE : View.VISIBLE);
+ mSummary.setText(summary);
+ }
+
+ void setTitle(CharSequence title) {
+ if (title == null) {
+ return;
+ }
+ mTitle.setText(title);
+ }
+
+ void setIcon(Drawable icon) {
+ mIcon.setImageDrawable(icon);
+ }
+
+ void setPackageName(String pkg) {
+ mPkgName.setText(pkg);
+ }
+
+ void setPostedTime(long postedTime) {
+ mTime.setTime(postedTime);
+ }
+
+ void setProfileBadge(Drawable badge) {
+ mProfileBadge.setImageDrawable(badge);
+ }
+}