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

Commit ce4f2e9d authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Show bundles separately from other channels" into main

parents 8a766c1b 2956baaf
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -8483,6 +8483,9 @@
    <!-- Configure Notifications: Title for the notification badging option. [CHAR LIMIT=50 BACKUP_MESSAGE_ID=5125022693565388760] -->
    <string name="notification_badging_title">Notification dot on app icon</string>
    <!-- App Info > Notifications: Title for section where notifications bundles can be configured [CHAR LIMIT=80]-->
    <string name="notification_bundles">Notification bundles</string>
    <!-- Configure Notifications: Title for the notification bubbles option. [CHAR LIMIT=60] -->
    <string name="notification_bubbles_title">Bubbles</string>
    <!-- Title for the toggle shown on the app-level bubbles page  [CHAR LIMIT=60] -->
+6 −0
Original line number Diff line number Diff line
@@ -50,6 +50,12 @@
        settings:controller="com.android.settings.notification.app.BubbleSummaryPreferenceController">
    </Preference>

    <!-- Bundles added here -->
    <PreferenceCategory
        android:key="bundles"
        android:title="@string/notification_bundles"
        android:visibility="gone" />

    <!-- Channels/Channel groups added here -->
    <PreferenceCategory
        android:key="channels"
+1 −0
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ public class AppNotificationSettings extends NotificationSettings {
        mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
        mControllers.add(new ShowMorePreferenceController(
                context, mDependentFieldListener, mBackend));
        mControllers.add(new BundleListPreferenceController(context, mBackend));
        return new ArrayList<>(mControllers);
    }
}
+171 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.app;

import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;

import static com.android.server.notification.Flags.notificationHideUnusedChannels;

import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.Flags;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.TwoStatePreference;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BundleListPreferenceController extends NotificationPreferenceController {

    private static final String KEY = "bundles";

    public BundleListPreferenceController(Context context, NotificationBackend backend) {
        super(context, backend);
    }

    @Override
    public String getPreferenceKey() {
        return KEY;
    }

    @Override
    public boolean isAvailable() {
        if (!Flags.notificationClassification()) {
            return false;
        }
        if (mAppRow == null) {
            return false;
        }
        if (mAppRow.banned || mAppRow.lockedImportance || mAppRow.systemApp) {
            return false;
        }
        return true;
    }

    @Override
    boolean isIncludedInFilter() {
        return false;
    }

    @Override
    public void updateState(Preference preference) {
        PreferenceCategory category = (PreferenceCategory) preference;

        createOrUpdatePrefForChannel(category,
                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID));
        createOrUpdatePrefForChannel(category,
                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID));
        createOrUpdatePrefForChannel(category,
                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID));
        createOrUpdatePrefForChannel(category,
                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID));
    }

    @NonNull
    private void createOrUpdatePrefForChannel(
            @NonNull PreferenceGroup groupPrefGroup, NotificationChannel channel) {
        int preferenceCount = groupPrefGroup.getPreferenceCount();
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = groupPrefGroup.getPreference(i);
            if (channel.getId().equals(preference.getKey())) {
                updateSingleChannelPrefs((PrimarySwitchPreference) preference, channel);
                return;
            }
        }
        PrimarySwitchPreference channelPref = new PrimarySwitchPreference(mContext);
        channelPref.setKey(channel.getId());
        updateSingleChannelPrefs(channelPref, channel);
        groupPrefGroup.addPreference(channelPref);
    }

    /** Update the properties of the channel preference with the values from the channel object. */
    private void updateSingleChannelPrefs(@NonNull final PrimarySwitchPreference channelPref,
            @NonNull final NotificationChannel channel) {
        channelPref.setSwitchEnabled(mAdmin == null);
        if (channel.getImportance() > IMPORTANCE_LOW) {
            channelPref.setIcon(getAlertingIcon());
        } else {
            channelPref.setIcon(mContext.getDrawable(R.drawable.empty_icon));
        }
        channelPref.setIconSize(PrimarySwitchPreference.ICON_SIZE_SMALL);
        channelPref.setTitle(channel.getName());
        channelPref.setSummary(NotificationBackend.getSentSummary(
                mContext, mAppRow.sentByChannel.get(channel.getId()), false));
        channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE);
        Bundle channelArgs = new Bundle();
        channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
        channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
        channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
        channelPref.setIntent(new SubSettingLauncher(mContext)
                .setDestination(ChannelNotificationSettings.class.getName())
                .setArguments(channelArgs)
                .setTitleRes(R.string.notification_channel_title)
                .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
                .toIntent());

        channelPref.setOnPreferenceChangeListener(
                (preference, o) -> {
                    boolean value = (Boolean) o;
                    int importance = value
                            ? Math.max(channel.getOriginalImportance(), IMPORTANCE_LOW)
                            : IMPORTANCE_NONE;
                    channel.setImportance(importance);
                    channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
                    PrimarySwitchPreference channelPref1 = (PrimarySwitchPreference) preference;
                    channelPref1.setIcon(R.drawable.empty_icon);
                    if (channel.getImportance() > IMPORTANCE_LOW) {
                        channelPref1.setIcon(getAlertingIcon());
                    }
                    mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);

                    return true;
                });
    }

    private Drawable getAlertingIcon() {
        Drawable icon = mContext.getDrawable(R.drawable.ic_notifications_alert);
        icon.setTintList(Utils.getColorAccent(mContext));
        return icon;
    }

}
+160 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.app;

import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.when;

import android.app.NotificationChannel;
import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Flags;

import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;

import com.google.common.collect.ImmutableMap;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
@SmallTest
@EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
public class BundleListPreferenceControllerTest {
    private Context mContext;
    @Mock
    private NotificationBackend mBackend;
    private NotificationBackend.AppRow mAppRow;
    private BundleListPreferenceController mController;
    private PreferenceManager mPreferenceManager;
    private PreferenceScreen mPreferenceScreen;
    private PreferenceCategory mGroupList;
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mContext = ApplicationProvider.getApplicationContext();

        mAppRow = new NotificationBackend.AppRow();
        mAppRow.pkg = "pkg";
        mAppRow.uid = 1111111;
        NotificationBackend.NotificationsSentState
                sentA = new NotificationBackend.NotificationsSentState();
        sentA.avgSentDaily = 2;
        sentA.avgSentWeekly = 10;
        NotificationBackend.NotificationsSentState
                sentB = new NotificationBackend.NotificationsSentState();
        sentB.avgSentDaily = 0;
        sentB.avgSentWeekly = 2;
        mAppRow.sentByChannel = ImmutableMap.of(
                PROMOTIONS_ID, sentA, NEWS_ID, sentA, SOCIAL_MEDIA_ID, sentB, RECS_ID, sentB);
        mController = new BundleListPreferenceController(mContext, mBackend);
        mController.onResume(mAppRow, null, null, null, null, null, null);
        mPreferenceManager = new PreferenceManager(mContext);
        mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
        mGroupList = new PreferenceCategory(mContext);
        mPreferenceScreen.addPreference(mGroupList);

        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
                new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));
        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID)).thenReturn(
                new NotificationChannel(NEWS_ID, NEWS_ID, 2));
        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID)).thenReturn(
                new NotificationChannel(SOCIAL_MEDIA_ID, SOCIAL_MEDIA_ID, 2));
        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID)).thenReturn(
                new NotificationChannel(RECS_ID, RECS_ID, 2));
    }

    @Test
    public void isAvailable_null() {
        mController.onResume(null, null, null, null, null, null, null);
        assertThat(mController.isAvailable()).isFalse();
        mAppRow.banned = true;
    }

    @Test
    public void isAvailable_banned() {
        mAppRow.banned = true;
        assertThat(mController.isAvailable()).isFalse();
    }

    @Test
    public void isAvailable_locked() {
        mAppRow.lockedImportance = true;
        assertThat(mController.isAvailable()).isFalse();
    }

    @Test
    public void isAvailable_system() {
        mAppRow.systemApp = true;
        assertThat(mController.isAvailable()).isFalse();
    }

    @Test
    public void isAvailable() {
        assertThat(mController.isAvailable()).isTrue();
    }

    @Test
    public void updateState() {
        mController.updateState(mGroupList);
        assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);
        assertThat(mGroupList.findPreference(PROMOTIONS_ID).getTitle()).isEqualTo(PROMOTIONS_ID);
        assertThat(mGroupList.findPreference(NEWS_ID).getTitle()).isEqualTo(NEWS_ID);
        assertThat(mGroupList.findPreference(SOCIAL_MEDIA_ID).getTitle())
                .isEqualTo(SOCIAL_MEDIA_ID);
        assertThat(mGroupList.findPreference(RECS_ID).getTitle()).isEqualTo(RECS_ID);
    }

    @Test
    public void updateState_updateChildren() {
        mController.updateState(mGroupList);
        assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);

        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
                new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));

        mController.updateState(mGroupList);
        assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);

        assertThat(((PrimarySwitchPreference) mGroupList.findPreference(NEWS_ID)).isChecked())
                .isEqualTo(false);
        assertThat(((PrimarySwitchPreference) mGroupList.findPreference(NEWS_ID)).isChecked())
                .isEqualTo(false);
    }
}