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

Commit 4e10ac8d authored by Alexander Roederer's avatar Alexander Roederer Committed by Android (Google) Code Review
Browse files

Merge changes from topic "b308819928-ModesSettingsAppBreakthroughPage" into main

* changes:
  Moves ZenModeBypassingAppsSettings into modes dir
  Adds modes settings page for bypassing apps
parents 310fc717 c5045761
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -8020,6 +8020,7 @@
        }
    </string>
    <!-- Do not disturb: restrict notifications settings title [CHAR LIMIT=80] -->
    <string name="zen_mode_restrict_notifications_title">Display options for filtered
        notifications</string>
@@ -9175,6 +9176,17 @@
    <!-- [CHAR LIMIT=50] Zen mode settings: Events (ie: calendar events) category in a list of sounds when events is the first or only element in the list. For example “Events can interrupt" or “Events and touch sounds can interrupt" -->
    <string name="zen_mode_events_list_first">Events</string>
    <!-- [CHAR LIMIT=80] Zen mode settings: Title for page to select which apps allowed to interrupt dnd -->
    <string name="zen_mode_apps_title">Apps</string>
    <!-- [CHAR LIMIT=100] Zen mode settings: subtitle for page to select which apps allowed to interrupt dnd -->
    <string name="zen_mode_apps_category">Apps that can interrupt</string>
    <!-- [CHAR LIMIT=60] Zen mode settings: selected apps will be able to bypass dnd -->
    <string name="zen_mode_apps_priority_apps">Selected apps</string>
    <!-- [CHAR LIMIT=60] Zen mode settings: no apps will be able to bypass dnd -->
    <string name="zen_mode_apps_none_apps">None</string>
    <!-- [CHAR LIMIT=60] Zen mode settings: all apps will be able to bypass dnd -->
    <string name="zen_mode_apps_all_apps">All</string>
    <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND -->
    <string name="zen_mode_bypassing_apps">Allow apps to override</string>
    <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header -->
+4 −0
Original line number Diff line number Diff line
@@ -34,6 +34,10 @@
                android:key="zen_mode_people"
                android:title="@string/zen_category_people"/>

        <Preference
            android:key="zen_mode_apps"
            android:title="@string/zen_category_apps" />

        <Preference
                android:key="zen_other_settings"
                android:title="@string/zen_category_exceptions" />
+44 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     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.
-->

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/zen_mode_apps_title" >

    <PreferenceCategory
        android:key="zen_mode_apps_category"
        android:title="@string/zen_mode_apps_category">

        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="zen_mode_apps_priority"
            android:title="@string/zen_mode_apps_priority_apps"
            settings:searchable="false"/>

        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="zen_mode_apps_none"
            android:title="@string/zen_mode_apps_none_apps"
            settings:searchable="false"/>

        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="zen_mode_apps_all"
            android:title="@string/zen_mode_apps_all_apps"
            settings:searchable="false"/>

    </PreferenceCategory>

</PreferenceScreen>
 No newline at end of file
+37 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     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.
-->

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/zen_mode_bypassing_apps_title">

    <PreferenceCategory
        android:key="zen_mode_bypassing_apps_list"
        android:title="@string/zen_mode_bypassing_apps_header">
        <!-- apps that have notifications that can bypass DND are added here -->
    </PreferenceCategory>

    <Preference
        android:key="zen_mode_bypassing_apps_add"
        android:title="@string/zen_mode_bypassing_apps_add"
        android:icon="@drawable/ic_add_24dp"
        settings:allowDividerAbove="true" />

    <com.android.settingslib.widget.FooterPreference
        android:title="@string/zen_mode_bypassing_apps_footer" />
</PreferenceScreen>
+260 −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.modes;

import android.app.Application;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AppPreference;

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


/**
 * When clicked, populates the PreferenceScreen with apps that aren't already bypassing DND. The
 * user can click on these Preferences to allow notification channels from the app to bypass DND.
 */
public class ZenModeAddBypassingAppsPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin {

    public static final String KEY_NO_APPS = "add_none";
    private static final String KEY = "zen_mode_non_bypassing_apps_list";
    private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
    @Nullable private final NotificationBackend mNotificationBackend;

    @Nullable @VisibleForTesting ApplicationsState mApplicationsState;
    @VisibleForTesting PreferenceScreen mPreferenceScreen;
    @VisibleForTesting PreferenceCategory mPreferenceCategory;
    @VisibleForTesting Context mPrefContext;

    private Preference mAddPreference;
    private ApplicationsState.Session mAppSession;
    @Nullable private Fragment mHostFragment;

    public ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable Application app,
            @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) {
        this(context, app == null ? null : ApplicationsState.getInstance(app), host,
                notificationBackend);
    }

    private ZenModeAddBypassingAppsPreferenceController(Context context,
            @Nullable ApplicationsState appState, @Nullable Fragment host,
            @Nullable NotificationBackend notificationBackend) {
        super(context);
        mNotificationBackend = notificationBackend;
        mApplicationsState = appState;
        mHostFragment = host;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        mPreferenceScreen = screen;
        mAddPreference = screen.findPreference(KEY_ADD);
        mAddPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                mAddPreference.setVisible(false);
                if (mApplicationsState != null && mHostFragment != null) {
                    mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
                            mHostFragment.getLifecycle());
                }
                return true;
            }
        });
        mPrefContext = screen.getContext();
        super.displayPreference(screen);
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

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

    /**
     * Call this method to trigger the app list to refresh.
     */
    public void updateAppList() {
        if (mAppSession == null) {
            return;
        }

        ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
                && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
                ? ApplicationsState.FILTER_ENABLED_NOT_QUIET
                : ApplicationsState.FILTER_ALL_ENABLED;
        mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR);
    }

    // Set the icon for the given preference to the entry icon from cache if available, or look
    // it up.
    private void updateIcon(Preference pref, ApplicationsState.AppEntry entry) {
        synchronized (entry) {
            final Drawable cachedIcon = AppUtils.getIconFromCache(entry);
            if (cachedIcon != null && entry.mounted) {
                pref.setIcon(cachedIcon);
            } else {
                ThreadUtils.postOnBackgroundThread(() -> {
                    final Drawable icon = AppUtils.getIcon(mPrefContext, entry);
                    if (icon != null) {
                        ThreadUtils.postOnMainThread(() -> pref.setIcon(icon));
                    }
                });
            }
        }
    }

    @VisibleForTesting
    void updateAppList(List<ApplicationsState.AppEntry> apps) {
        if (apps == null) {
            return;
        }

        if (mPreferenceCategory == null) {
            mPreferenceCategory = new PreferenceCategory(mPrefContext);
            mPreferenceCategory.setTitle(R.string.zen_mode_bypassing_apps_add_header);
            mPreferenceScreen.addPreference(mPreferenceCategory);
        }

        boolean doAnyAppsPassCriteria = false;
        for (ApplicationsState.AppEntry app : apps) {
            String pkg = app.info.packageName;
            final String key = getKey(pkg, app.info.uid);
            final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
            final int appChannelsBypassingDnd = mNotificationBackend
                    .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
            if (appChannelsBypassingDnd == 0 && appChannels > 0) {
                doAnyAppsPassCriteria = true;
            }

            Preference pref = mPreferenceCategory.findPreference(key);

            if (pref == null) {
                if (appChannelsBypassingDnd == 0 && appChannels > 0) {
                    // does not exist but should
                    pref = new AppPreference(mPrefContext);
                    pref.setKey(key);
                    pref.setOnPreferenceClickListener(preference -> {
                        Bundle args = new Bundle();
                        args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName);
                        args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid);
                        new SubSettingLauncher(mContext)
                                .setDestination(AppChannelsBypassingDndSettings.class.getName())
                                .setArguments(args)
                                .setResultListener(mHostFragment, 0)
                                .setUserHandle(new UserHandle(UserHandle.getUserId(app.info.uid)))
                                .setSourceMetricsCategory(
                                        SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
                                .launch();
                        return true;
                    });
                    pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
                    updateIcon(pref, app);
                    mPreferenceCategory.addPreference(pref);
                }
            } else if (appChannelsBypassingDnd != 0 || appChannels == 0) {
                // exists but shouldn't anymore
                mPreferenceCategory.removePreference(pref);
            }
        }

        Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS);
        if (!doAnyAppsPassCriteria) {
            if (pref == null) {
                pref = new Preference(mPrefContext);
                pref.setKey(KEY_NO_APPS);
                pref.setTitle(R.string.zen_mode_bypassing_apps_none);
            }
            mPreferenceCategory.addPreference(pref);
        } else if (pref != null) {
            mPreferenceCategory.removePreference(pref);
        }
    }

    static String getKey(String pkg, int uid) {
        return "add|" + pkg + "|" + uid;
    }

    private final ApplicationsState.Callbacks mAppSessionCallbacks =
            new ApplicationsState.Callbacks() {

                @Override
                public void onRunningStateChanged(boolean running) {

                }

                @Override
                public void onPackageListChanged() {

                }

                @Override
                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
                    updateAppList(apps);
                }

                @Override
                public void onPackageIconChanged() {
                    updateAppList();
                }

                @Override
                public void onPackageSizeChanged(String packageName) {

                }

                @Override
                public void onAllSizesComputed() { }

                @Override
                public void onLauncherInfoChanged() {

                }

                @Override
                public void onLoadEntriesCompleted() {
                    updateAppList();
                }
            };
}
Loading