Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7564be5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents b96e56b1 316c7633
Loading
Loading
Loading
Loading
+29 −29
Original line number Diff line number Diff line
@@ -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;
            }
        }
+53 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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;
+81 −0
Original line number Diff line number Diff line
/*
 * 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));
    }
}