Merge "Improve the latency of NotificationChannelSlice" into qt-dev

am: c7564be562

Change-Id: I86c35f4c8d4dde32592ddec09926e226dbcc0751
This commit is contained in:
Yanting Yang
2019-04-30 00:01:51 -07:00
committed by android-build-merger
4 changed files with 164 additions and 29 deletions

View File

@@ -66,7 +66,12 @@ import com.android.settingslib.applications.ApplicationsState;
import java.util.ArrayList;
import java.util.Comparator;
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.TimeoutException;
import java.util.stream.Collectors;
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_UID = "package_uid";
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.
@@ -129,6 +135,7 @@ public class NotificationChannelSlice implements CustomSliceable {
};
protected final Context mContext;
private final ExecutorService mExecutorService;
@VisibleForTesting
NotificationBackend mNotificationBackend;
private NotificationBackend.AppRow mAppRow;
@@ -138,6 +145,7 @@ public class NotificationChannelSlice implements CustomSliceable {
public NotificationChannelSlice(Context context) {
mContext = context;
mNotificationBackend = new NotificationBackend();
mExecutorService = Executors.newCachedThreadPool();
}
@Override
@@ -151,10 +159,7 @@ public class NotificationChannelSlice implements CustomSliceable {
* 2. Multiple channels.
* 3. Sent at least ~10 notifications.
*/
// TODO(b/123065955): Review latency of NotificationChannelSlice
final List<PackageInfo> multiChannelPackages = getMultiChannelPackages(
getRecentlyInstalledPackages());
mPackageName = getMaxSentNotificationsPackage(multiChannelPackages);
mPackageName = getEligibleNotificationsPackage(getRecentlyInstalledPackages());
if (mPackageName == null) {
// Return a header with IsError flag, if package is not found.
return listBuilder.setHeader(getNoSuggestedAppHeader())
@@ -306,25 +311,6 @@ public class NotificationChannelSlice implements CustomSliceable {
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() {
final long startTime = System.currentTimeMillis() - DURATION_START_DAYS;
final long endTime = System.currentTimeMillis() - DURATION_END_DAYS;
@@ -383,19 +369,33 @@ public class NotificationChannelSlice implements CustomSliceable {
.collect(Collectors.toList());
}
private String getMaxSentNotificationsPackage(List<PackageInfo> packageInfoList) {
private String getEligibleNotificationsPackage(List<PackageInfo> packageInfoList) {
if (packageInfoList.isEmpty()) {
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.
int maxSentCount = 0;
String maxSentCountPackage = null;
for (PackageInfo packageInfo : packageInfoList) {
final NotificationBackend.AppRow appRow = mNotificationBackend.loadAppRow(mContext,
mContext.getPackageManager(), packageInfo);
for (Future<NotificationBackend.AppRow> appRowTask : appRowTasks) {
NotificationBackend.AppRow appRow = null;
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.
if (appRow.banned || isAllChannelsBlocked(getDisplayableChannels(appRow))) {
if (appRow == null || appRow.banned || isAllChannelsBlocked(
getDisplayableChannels(appRow))) {
continue;
}
@@ -403,7 +403,7 @@ public class NotificationChannelSlice implements CustomSliceable {
final int sentCount = appRow.sentByApp.sentCount;
if (sentCount >= MIN_NOTIFICATION_SENT_COUNT && sentCount > maxSentCount) {
maxSentCount = sentCount;
maxSentCountPackage = packageInfo.packageName;
maxSentCountPackage = appRow.pkg;
mAppRow = appRow;
}
}

View File

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

View File

@@ -319,6 +319,7 @@ public class NotificationChannelSliceTest {
applicationInfo.name = APP_LABEL;
applicationInfo.uid = UID;
applicationInfo.flags = flags;
applicationInfo.packageName = PACKAGE_NAME;
final PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = PACKAGE_NAME;

View File

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