Improve the latency of NotificationChannelSlice
Get the notification data in parallel to reduce the latency. Fixes: 123065955 Test: visual, robotests Change-Id: I434ec68197af214f540ba39ae9155ee7625a2742
This commit is contained in:
@@ -66,7 +66,12 @@ import com.android.settingslib.applications.ApplicationsState;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class NotificationChannelSlice implements CustomSliceable {
|
public class NotificationChannelSlice implements CustomSliceable {
|
||||||
@@ -98,6 +103,7 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
private static final String PACKAGE_NAME = "package_name";
|
private static final String PACKAGE_NAME = "package_name";
|
||||||
private static final String PACKAGE_UID = "package_uid";
|
private static final String PACKAGE_UID = "package_uid";
|
||||||
private static final String CHANNEL_ID = "channel_id";
|
private static final String CHANNEL_ID = "channel_id";
|
||||||
|
private static final long TASK_TIMEOUT_MS = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort notification channel with weekly average sent count by descending.
|
* Sort notification channel with weekly average sent count by descending.
|
||||||
@@ -129,6 +135,7 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
|
private final ExecutorService mExecutorService;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
NotificationBackend mNotificationBackend;
|
NotificationBackend mNotificationBackend;
|
||||||
private NotificationBackend.AppRow mAppRow;
|
private NotificationBackend.AppRow mAppRow;
|
||||||
@@ -138,6 +145,7 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
public NotificationChannelSlice(Context context) {
|
public NotificationChannelSlice(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mNotificationBackend = new NotificationBackend();
|
mNotificationBackend = new NotificationBackend();
|
||||||
|
mExecutorService = Executors.newCachedThreadPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -151,10 +159,7 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
* 2. Multiple channels.
|
* 2. Multiple channels.
|
||||||
* 3. Sent at least ~10 notifications.
|
* 3. Sent at least ~10 notifications.
|
||||||
*/
|
*/
|
||||||
// TODO(b/123065955): Review latency of NotificationChannelSlice
|
mPackageName = getEligibleNotificationsPackage(getRecentlyInstalledPackages());
|
||||||
final List<PackageInfo> multiChannelPackages = getMultiChannelPackages(
|
|
||||||
getRecentlyInstalledPackages());
|
|
||||||
mPackageName = getMaxSentNotificationsPackage(multiChannelPackages);
|
|
||||||
if (mPackageName == null) {
|
if (mPackageName == null) {
|
||||||
// Return a header with IsError flag, if package is not found.
|
// Return a header with IsError flag, if package is not found.
|
||||||
return listBuilder.setHeader(getNoSuggestedAppHeader())
|
return listBuilder.setHeader(getNoSuggestedAppHeader())
|
||||||
@@ -306,25 +311,6 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
return PendingIntent.getBroadcast(mContext, intent.hashCode(), intent, 0);
|
return PendingIntent.getBroadcast(mContext, intent.hashCode(), intent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PackageInfo> getMultiChannelPackages(List<PackageInfo> packageInfoList) {
|
|
||||||
final List<PackageInfo> multiChannelPackages = new ArrayList<>();
|
|
||||||
|
|
||||||
if (packageInfoList.isEmpty()) {
|
|
||||||
return multiChannelPackages;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PackageInfo packageInfo : packageInfoList) {
|
|
||||||
final int channelCount = mNotificationBackend.getChannelCount(packageInfo.packageName,
|
|
||||||
getApplicationUid(packageInfo.packageName));
|
|
||||||
if (channelCount > 1) {
|
|
||||||
multiChannelPackages.add(packageInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(b/119831690): Filter the packages which doesn't have any configurable channel.
|
|
||||||
return multiChannelPackages;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PackageInfo> getRecentlyInstalledPackages() {
|
private List<PackageInfo> getRecentlyInstalledPackages() {
|
||||||
final long startTime = System.currentTimeMillis() - DURATION_START_DAYS;
|
final long startTime = System.currentTimeMillis() - DURATION_START_DAYS;
|
||||||
final long endTime = System.currentTimeMillis() - DURATION_END_DAYS;
|
final long endTime = System.currentTimeMillis() - DURATION_END_DAYS;
|
||||||
@@ -383,19 +369,33 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMaxSentNotificationsPackage(List<PackageInfo> packageInfoList) {
|
private String getEligibleNotificationsPackage(List<PackageInfo> packageInfoList) {
|
||||||
if (packageInfoList.isEmpty()) {
|
if (packageInfoList.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tasks to get notification data for multi-channel packages.
|
||||||
|
final List<Future<NotificationBackend.AppRow>> appRowTasks = new ArrayList<>();
|
||||||
|
for (PackageInfo packageInfo : packageInfoList) {
|
||||||
|
final NotificationMultiChannelAppRow future = new NotificationMultiChannelAppRow(
|
||||||
|
mContext, mNotificationBackend, packageInfo);
|
||||||
|
appRowTasks.add(mExecutorService.submit(future));
|
||||||
|
}
|
||||||
|
|
||||||
// Get the package which has sent at least ~10 notifications and not turn off channels.
|
// Get the package which has sent at least ~10 notifications and not turn off channels.
|
||||||
int maxSentCount = 0;
|
int maxSentCount = 0;
|
||||||
String maxSentCountPackage = null;
|
String maxSentCountPackage = null;
|
||||||
for (PackageInfo packageInfo : packageInfoList) {
|
for (Future<NotificationBackend.AppRow> appRowTask : appRowTasks) {
|
||||||
final NotificationBackend.AppRow appRow = mNotificationBackend.loadAppRow(mContext,
|
NotificationBackend.AppRow appRow = null;
|
||||||
mContext.getPackageManager(), packageInfo);
|
try {
|
||||||
|
appRow = appRowTask.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
|
Log.w(TAG, "Failed to get notification data.", e);
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore packages which are banned notifications or block all displayable channels.
|
// Ignore packages which are banned notifications or block all displayable channels.
|
||||||
if (appRow.banned || isAllChannelsBlocked(getDisplayableChannels(appRow))) {
|
if (appRow == null || appRow.banned || isAllChannelsBlocked(
|
||||||
|
getDisplayableChannels(appRow))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
final int sentCount = appRow.sentByApp.sentCount;
|
final int sentCount = appRow.sentByApp.sentCount;
|
||||||
if (sentCount >= MIN_NOTIFICATION_SENT_COUNT && sentCount > maxSentCount) {
|
if (sentCount >= MIN_NOTIFICATION_SENT_COUNT && sentCount > maxSentCount) {
|
||||||
maxSentCount = sentCount;
|
maxSentCount = sentCount;
|
||||||
maxSentCountPackage = packageInfo.packageName;
|
maxSentCountPackage = appRow.pkg;
|
||||||
mAppRow = appRow;
|
mAppRow = appRow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
|
||||||
|
import com.android.settings.notification.NotificationBackend;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for getting notification app row from package which has multiple
|
||||||
|
* notification channels.{@link NotificationChannelSlice} uses it to improve latency.
|
||||||
|
*/
|
||||||
|
class NotificationMultiChannelAppRow implements Callable<NotificationBackend.AppRow> {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final NotificationBackend mNotificationBackend;
|
||||||
|
private final PackageInfo mPackageInfo;
|
||||||
|
|
||||||
|
public NotificationMultiChannelAppRow(Context context, NotificationBackend notificationBackend,
|
||||||
|
PackageInfo packageInfo) {
|
||||||
|
mContext = context;
|
||||||
|
mNotificationBackend = notificationBackend;
|
||||||
|
mPackageInfo = packageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NotificationBackend.AppRow call() throws Exception {
|
||||||
|
final int channelCount = mNotificationBackend.getChannelCount(
|
||||||
|
mPackageInfo.applicationInfo.packageName, mPackageInfo.applicationInfo.uid);
|
||||||
|
if (channelCount > 1) {
|
||||||
|
return mNotificationBackend.loadAppRow(mContext, mContext.getPackageManager(),
|
||||||
|
mPackageInfo);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -318,6 +318,7 @@ public class NotificationChannelSliceTest {
|
|||||||
applicationInfo.name = APP_LABEL;
|
applicationInfo.name = APP_LABEL;
|
||||||
applicationInfo.uid = UID;
|
applicationInfo.uid = UID;
|
||||||
applicationInfo.flags = flags;
|
applicationInfo.flags = flags;
|
||||||
|
applicationInfo.packageName = PACKAGE_NAME;
|
||||||
|
|
||||||
final PackageInfo packageInfo = new PackageInfo();
|
final PackageInfo packageInfo = new PackageInfo();
|
||||||
packageInfo.packageName = PACKAGE_NAME;
|
packageInfo.packageName = PACKAGE_NAME;
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
import com.android.settings.notification.NotificationBackend;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class NotificationMultiChannelAppRowTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationBackend mNotificationBackend;
|
||||||
|
private Context mContext;
|
||||||
|
private NotificationMultiChannelAppRow mNotificationMultiChannelAppRow;
|
||||||
|
private PackageInfo mPackageInfo;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mPackageInfo = new PackageInfo();
|
||||||
|
mPackageInfo.applicationInfo = new ApplicationInfo();
|
||||||
|
mPackageInfo.applicationInfo.packageName = "com.android.test";
|
||||||
|
mNotificationMultiChannelAppRow = new NotificationMultiChannelAppRow(mContext,
|
||||||
|
mNotificationBackend, mPackageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void call_isMultiChannel_shouldLoadAppRow() throws Exception {
|
||||||
|
doReturn(3).when(mNotificationBackend).getChannelCount(any(String.class),
|
||||||
|
any(int.class));
|
||||||
|
|
||||||
|
mNotificationMultiChannelAppRow.call();
|
||||||
|
|
||||||
|
verify(mNotificationBackend).loadAppRow(any(Context.class), any(PackageManager.class),
|
||||||
|
any(PackageInfo.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void call_isNotMultiChannel_shouldNotLoadAppRow() throws Exception {
|
||||||
|
doReturn(1).when(mNotificationBackend).getChannelCount(any(String.class),
|
||||||
|
any(int.class));
|
||||||
|
|
||||||
|
mNotificationMultiChannelAppRow.call();
|
||||||
|
|
||||||
|
verify(mNotificationBackend, never()).loadAppRow(any(Context.class),
|
||||||
|
any(PackageManager.class), any(PackageInfo.class));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user