Add screen for notification history
Bug: 137396965
Test: manual
(cherry picked from commit 84b593d592a74aeddfce453bc8700180192199ed)
Change-Id: Icac4000bd7323583f8a5f462ccb40826a4151765
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9bb810d..e70d468 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1193,6 +1193,15 @@
android:value="com.android.settings.notification.history.NotificationStation" />
</activity>
+ <activity
+ android:name=".notification.history.NotificationHistoryActivity"
+ android:label="@string/notification_history_title">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".notification.zen.ZenModeVoiceActivity"
android:theme="@*android:style/Theme.DeviceDefault.Settings.Dialog.NoActionBar"
android:label="@string/zen_mode_settings_title">
diff --git a/res/drawable/ic_clear.xml b/res/drawable/ic_clear.xml
new file mode 100644
index 0000000..224425f
--- /dev/null
+++ b/res/drawable/ic_clear.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12Z"
+ android:fillColor="#FFFFFF" />
+</vector>
+
diff --git a/res/drawable/ic_snooze.xml b/res/drawable/ic_snooze.xml
new file mode 100644
index 0000000..d7bbbaf
--- /dev/null
+++ b/res/drawable/ic_snooze.xml
@@ -0,0 +1,24 @@
+<!--
+ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
+</vector>
diff --git a/res/drawable/ic_today.xml b/res/drawable/ic_today.xml
new file mode 100644
index 0000000..1d39610
--- /dev/null
+++ b/res/drawable/ic_today.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM9,9.5c-1.38,0 -2.5,1.12 -2.5,2.5s1.12,2.5 2.5,2.5 2.5,-1.12 2.5,-2.5S10.38,9.5 9,9.5z"/>
+</vector>
\ 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 0000000..fcdd761
--- /dev/null
+++ b/res/drawable/rounded_bg.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackgroundFloating" />
+ <corners
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="?android:attr/dialogCornerRadius"
+ />
+</shape>
\ 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 0000000..91a59a8
--- /dev/null
+++ b/res/layout/notification_history.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/scroll"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:background="@*android:color/material_grey_50">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- TODO: header switch -->
+ <LinearLayout
+ android:id="@+id/snoozed_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginBottom="10dp">
+ <LinearLayout
+ android:id="@+id/snooze_header"
+ android:layout_height="48dp"
+ android:layout_width="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <ImageView
+ android:src="@drawable/ic_snooze"
+ android:tint="?android:attr/textColorPrimary"
+ android:padding="6dp"
+ android:layout_height="36dp"
+ android:layout_width="36dp" />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/notification_history_snooze"
+ android:textColor="?android:attr/textColorPrimary"
+ android:paddingStart="@dimen/notification_history_header_drawable_start" />
+ </LinearLayout>
+ <FrameLayout
+ android:id="@+id/list_container"
+ android:layout_width="wrap_content"
+ android:layout_height="300dp"
+ android:clipChildren="true"
+ android:elevation="3dp"
+ android:background="@drawable/rounded_bg">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/notification_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ settings:fastScrollEnabled="true"
+ settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
+ settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
+
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/recently_dismissed_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginBottom="10dp">
+ <LinearLayout
+ android:id="@+id/dismissed_header"
+ android:layout_height="48dp"
+ android:layout_width="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <ImageView
+ android:src="@drawable/ic_clear"
+ android:tint="?android:attr/textColorPrimary"
+ android:padding="6dp"
+ android:layout_height="36dp"
+ android:layout_width="36dp" />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/notification_history_dismiss"
+ android:textColor="?android:attr/textColorPrimary"
+ android:paddingStart="@dimen/notification_history_header_drawable_start" />
+ </LinearLayout>
+ <FrameLayout
+ android:id="@+id/list_container"
+ android:layout_width="wrap_content"
+ android:layout_height="300dp"
+ android:elevation="3dp"
+ android:clipChildren="true"
+ android:background="@drawable/rounded_bg">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/notification_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ settings:fastScrollEnabled="true"
+ settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
+ settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
+
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/today_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/app_header"
+ android:layout_height="48dp"
+ android:layout_width="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <ImageView
+ android:src="@drawable/ic_today"
+ android:tint="?android:attr/textColorPrimary"
+ android:padding="6dp"
+ android:layout_height="36dp"
+ android:layout_width="36dp" />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/notification_history_today"
+ android:textColor="?android:attr/textColorPrimary"
+ android:paddingStart="@dimen/notification_history_header_drawable_start" />
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/apps"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="3dp"
+ android:orientation="vertical"
+ android:background="@drawable/rounded_bg">
+ <!-- app based recycler views added here -->
+ </LinearLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+</ScrollView>
\ 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 0000000..6f26789
--- /dev/null
+++ b/res/layout/notification_history_app_layout.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!-- 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:id="@+id/app_header"
+ android:layout_height="48dp"
+ android:layout_width="match_parent">
+ <ImageView
+ android:id="@+id/icon"
+ android:padding="6dp"
+ android:layout_centerVertical="true"
+ android:layout_height="36dp"
+ android:layout_width="36dp" />
+ <TextView
+ android:id="@+id/label"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_centerVertical="true"
+
+ android:textColor="@color/material_grey_900"
+ android:layout_toEndOf="@id/icon"
+ android:paddingStart="@dimen/notification_history_header_drawable_start" />
+ <ImageButton
+ android:id="@+id/expand"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:layout_height="48dp"
+ android:layout_width="48dp"
+ android:src="@*android:drawable/ic_expand_more"/>
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/list_container"
+ android:layout_width="match_parent"
+ android:clipChildren="true"
+ android:layout_height="300dp">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/notification_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ settings:fastScrollEnabled="true"
+ settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
+ settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
+ settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
+
+ </FrameLayout>
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/material_grey_300" />
+
+</LinearLayout>
\ 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 0000000..4c38167
--- /dev/null
+++ b/res/layout/notification_history_log_row.xml
@@ -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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="6dp"
+ android:paddingTop="6dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="vertical"
+ android:background="?android:attr/selectableItemBackground">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginBottom="6dp">
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="@*android:dimen/status_bar_icon_size"
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/material_grey_900"
+ android:textAlignment="viewStart"/>
+
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@*android:dimen/status_bar_icon_size"
+ android:layout_height="@*android:dimen/status_bar_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="6dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:layout_toEndOf="@id/title"
+ android:tint="?android:attr/textColorSecondary"
+ android:src="@drawable/ic_notifications_alert"/>
+
+ <DateTimeView
+ android:id="@+id/timestamp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:layout_marginTop="17dp"
+ android:background="@color/material_grey_300" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/notification_sbn_log_row.xml b/res/layout/notification_sbn_log_row.xml
new file mode 100644
index 0000000..3c51abd
--- /dev/null
+++ b/res/layout/notification_sbn_log_row.xml
@@ -0,0 +1,142 @@
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="6dp"
+ android:paddingTop="6dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="vertical"
+ android:background="?android:attr/selectableItemBackground"
+ >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@*android:dimen/status_bar_icon_size"
+ android:layout_height="@*android:dimen/status_bar_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/pkgicon"
+ android:layout_marginStart="0dp"
+ android:layout_marginEnd="8dp"
+ android:contentDescription="@null"
+ android:adjustViewBounds="true"
+ android:tint="?android:attr/textColorPrimary"
+ android:maxHeight="@*android:dimen/status_bar_icon_size"
+ android:maxWidth="@*android:dimen/status_bar_icon_size"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/pkgname"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@id/icon"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:textAlignment="viewStart" />
+
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@*android:dimen/status_bar_icon_size"
+ android:layout_height="@*android:dimen/status_bar_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="6dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:layout_toEndOf="@id/pkgname"
+ android:tint="?android:attr/textColorSecondary"
+ android:src="@drawable/ic_notifications_alert"
+ />
+
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@*android:dimen/status_bar_icon_size"
+ android:layout_height="@*android:dimen/status_bar_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_marginEnd="6dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:layout_toStartOf="@id/timestamp"
+ />
+
+ <DateTimeView
+ android:id="@+id/timestamp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="13dp"
+ android:paddingBottom="13dp"
+ android:layout_alignBottom="@android:id/widget_frame"
+ android:layout_alignParentEnd="true"
+ android:layout_alignTop="@android:id/widget_frame"
+ android:layout_centerVertical="true"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ />
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginStart="30dp"
+ android:layout_marginBottom="6dp"
+ >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAlignment="viewStart"
+ />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart"
+ />
+
+ </LinearLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:layout_marginTop="17dp"
+ android:background="@color/material_grey_300" />
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b8afd60..e00eecb 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -81,6 +81,8 @@
<dimen name="notification_importance_button_text">16sp</dimen>
<dimen name="notification_importance_button_padding">14dp</dimen>
+ <dimen name="notification_history_header_drawable_start">26dp</dimen>
+
<dimen name="zen_mode_button_padding_vertical">16dp</dimen>
<dimen name="zen_schedule_rule_checkbox_padding">7dp</dimen>
<dimen name="zen_schedule_day_margin">17dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6dc4a4d..2efc339 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6147,6 +6147,11 @@
<string name="admin_more_details">More details</string>
<string name="notification_log_title">Notification log</string>
+ <string name="notification_history_title">Notification history</string>
+ <string name="notification_history_today">Today</string>
+ <string name="notification_history_snooze">Snoozed</string>
+ <string name="notification_history_dismiss">Recently dismissed</string>
+
<!-- Category title for phone call's ringtone and vibration settings in the Sound Setting.
[CHAR LIMIT=40] -->
<string name="sound_category_call_ringtone_vibrate_title">Call ringtone & vibrate</string>
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index a8396a1..f749764 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -21,6 +21,7 @@
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 @@
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 0000000..046b5ef
--- /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<String, NotificationHistoryPackage> 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<NotificationHistoryPackage> 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<NotificationHistoryPackage> 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 0000000..173508b
--- /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 0000000..f87bb20
--- /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<NotificationHistoryViewHolder> {
+
+ private List<HistoricalNotification> 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<HistoricalNotification> 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 0000000..5170b75
--- /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<NotificationHistory.HistoricalNotification> 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 0000000..35f5615
--- /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 0000000..80ba278
--- /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<NotificationSbnViewHolder> {
+
+ private static final String TAG = "SbnAdapter";
+ private List<StatusBarNotification> mValues;
+ private Map<Integer, Drawable> 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<StatusBarNotification> 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<Notification.MessagingStyle.Message> 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 0000000..79cda7f
--- /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);
+ }
+}