Merge "Add screen for notification history"

This commit is contained in:
Julia Reynolds
2020-01-21 16:21:30 +00:00
committed by Android (Google) Code Review
19 changed files with 1287 additions and 0 deletions

View File

@@ -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">

26
res/drawable/ic_clear.xml Normal file
View File

@@ -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>

View File

@@ -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>

24
res/drawable/ic_today.xml Normal file
View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -6163,6 +6163,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 &amp; vibrate</string>

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
};
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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));
});
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}