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

Commit c5045761 authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Moves ZenModeBypassingAppsSettings into modes dir

Copies these files into modes dir, so we can continue to deprecate the
old zen directory

Bug: 308819928
Test: flash+test, atest ZenModeAllBypassingAppsPreferenceControllerTest,
atestZenModeAddBypassingAppsPreferenceControllerTest
Flag: android.app.modes_ui
Change-Id: Ib18930bb16a3d675e460028f5c52ef32060541ad

Change-Id: Id6c8d834bbd106fef068c5f04acf8cd71229a5e3
parent c512046e
Loading
Loading
Loading
Loading
+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();
                }
            };
}
+244 −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;

/**
 * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
 */
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin {
    public static final String KEY_NO_APPS = "all_none";
    private static final String KEY = "zen_mode_bypassing_apps_list";

    @Nullable private final NotificationBackend mNotificationBackend;

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

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

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

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

        if (mApplicationsState != null && host != null) {
            mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
        }
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        mPreferenceCategory = screen.findPreference(KEY);
        mPrefContext = screen.getContext();
        updateAppList();
        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 (mPreferenceCategory == null || apps == null) {
            return;
        }

        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) {
                doAnyAppsPassCriteria = true;
            }

            Preference pref = mPreferenceCategory.findPreference(key);
            if (pref == null) {
                if (appChannelsBypassingDnd > 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)
                                .setUserHandle(UserHandle.getUserHandleForUid(app.info.uid))
                                .setResultListener(mHostFragment, 0)
                                .setSourceMetricsCategory(
                                        SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
                                .launch();
                        return true;
                    });
                    pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
                    updateIcon(pref, app);
                    if (appChannels > appChannelsBypassingDnd) {
                        pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some);
                    } else {
                        pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all);
                    }
                    mPreferenceCategory.addPreference(pref);
                }
            } else if (appChannelsBypassingDnd == 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);
        }
    }

    /**
     * Create a unique key to idenfity an AppPreference
     */
    static String getKey(String pkg, int uid) {
        return "all|" + 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() {
                }

                @Override
                public void onPackageSizeChanged(String packageName) {
                }

                @Override
                public void onAllSizesComputed() { }

                @Override
                public void onLauncherInfoChanged() {
                }

                @Override
                public void onLoadEntriesCompleted() {
                    updateAppList();
                }
            };
}
+12 −2
Original line number Diff line number Diff line
@@ -16,8 +16,11 @@

package com.android.settings.notification.modes;

import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.service.notification.ZenPolicy;

import androidx.annotation.NonNull;
@@ -28,7 +31,6 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;

import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.zen.ZenModeBypassingAppsSettings;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

public class ZenModeAppsPreferenceController extends
@@ -38,6 +40,8 @@ public class ZenModeAppsPreferenceController extends
    static final String KEY_NONE = "zen_mode_apps_none";
    static final String KEY_ALL = "zen_mode_apps_all";

    String mModeId;


    public ZenModeAppsPreferenceController(@NonNull Context context,
            @NonNull String key, @Nullable ZenModesBackend backend) {
@@ -62,6 +66,7 @@ public class ZenModeAppsPreferenceController extends

    @Override
    public void updateState(Preference preference, @NonNull ZenMode zenMode) {
        mModeId = zenMode.getId();
        TwoStatePreference pref = (TwoStatePreference) preference;
        switch (getPreferenceKey()) {
            case KEY_PRIORITY:
@@ -107,10 +112,15 @@ public class ZenModeAppsPreferenceController extends
            };

    private void launchPrioritySettings() {
        Bundle bundle = new Bundle();
        if (mModeId != null) {
            bundle.putString(MODE_ID, mModeId);
        }
        // TODO(b/332937635): Update metrics category
        new SubSettingLauncher(mContext)
                .setDestination(ZenModeBypassingAppsSettings.class.getName())
                .setDestination(ZenModeSelectBypassingAppsFragment.class.getName())
                .setSourceMetricsCategory(SettingsEnums.SETTINGS_ZEN_NOTIFICATIONS)
                .setArguments(bundle)
                .launch();
    }
}
+93 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading