Loading res/values/strings.xml +10 −0 Original line number Diff line number Diff line Loading @@ -9214,6 +9214,16 @@ <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=NONE] Zen mode settings: Lists apps that can bypass DND. For example, "Nest, Messages, and 2 more can interrupt". --> <string name="zen_mode_apps_subtext"> {count, plural, offset:2 =0 {No apps can interrupt} =1 {{app_1} can interrupt} =2 {{app_1} and {app_2} can interrupt} =3 {{app_1}, {app_2}, and {app_3} can interrupt} other {{app_1}, {app_2}, and # more can interrupt} } </string> <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND --> <string name="zen_mode_bypassing_apps">Allow apps to override</string> src/com/android/settings/notification/NotificationBackend.java +13 −0 Original line number Diff line number Diff line Loading @@ -357,6 +357,19 @@ public class NotificationBackend { } } /** * Returns all of a user's packages that have at least one channel that will bypass DND */ public List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels) { try { return sINM.getPackagesBypassingDnd(userId, includeConversationChannels); } catch (Exception e) { Log.w(TAG, "Error calling NoMan", e); return new ArrayList<>(); } } public void updateChannel(String pkg, int uid, NotificationChannel channel) { try { sINM.updateNotificationChannelForPackage(pkg, uid, channel); Loading src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +103 −4 Original line number Diff line number Diff line Loading @@ -20,23 +20,44 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I import android.content.Context; import android.os.Bundle; import android.util.ArraySet; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; import androidx.fragment.app.Fragment; import androidx.preference.Preference; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.applications.ApplicationsState; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Preference with a link and summary about what apps can break through the mode */ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController { class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController { private static final String TAG = "ZenModeAppsLinkPreferenceController"; private final ZenModeSummaryHelper mSummaryHelper; private ApplicationsState.Session mAppSession; private NotificationBackend mNotificationBackend = new NotificationBackend(); private ZenMode mZenMode; private Preference mPreference; public ZenModeAppsLinkPreferenceController(Context context, String key, ZenModesBackend backend) { ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host, ApplicationsState applicationsState, ZenModesBackend backend) { super(context, key, backend); mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend); if (applicationsState != null && host != null) { mAppSession = applicationsState.newSession(mAppSessionCallbacks, host.getLifecycle()); } } @Override Loading @@ -49,6 +70,84 @@ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferen .setSourceMetricsCategory(0) .setArguments(bundle) .toIntent()); preference.setSummary(mSummaryHelper.getAppsSummary(zenMode)); mZenMode = zenMode; mPreference = preference; triggerUpdateAppsBypassingDndSummaryText(); } private void triggerUpdateAppsBypassingDndSummaryText() { if (mAppSession == null) { return; } ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures() && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() ? ApplicationsState.FILTER_ENABLED_NOT_QUIET : ApplicationsState.FILTER_ALL_ENABLED; // We initiate a rebuild in the background here. Once the rebuild is completed, // the onRebuildComplete() callback will be invoked, which will trigger the summary text // to be initialized. mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR, false); } private void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) { Set<String> appNames = getAppsBypassingDnd(apps); mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, appNames)); } @VisibleForTesting ArraySet<String> getAppsBypassingDnd(@NonNull List<ApplicationsState.AppEntry> apps) { ArraySet<String> appsBypassingDnd = new ArraySet<>(); Map<String, String> pkgLabelMap = new HashMap<String, String>(); for (ApplicationsState.AppEntry entry : apps) { if (entry.info != null) { pkgLabelMap.put(entry.info.packageName, entry.label); } } for (String pkg : mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(), /* includeConversationChannels= */ false)) { // Settings may hide some packages from the user, so if they're not present here // we skip displaying them, even if they bypass dnd. if (pkgLabelMap.get(pkg) == null) { continue; } appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(pkgLabelMap.get(pkg))); } return appsBypassingDnd; } @VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks = new ApplicationsState.Callbacks() { @Override public void onRunningStateChanged(boolean running) { } @Override public void onPackageListChanged() { triggerUpdateAppsBypassingDndSummaryText(); } @Override public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { updateAppsBypassingDndSummaryText(apps); } @Override public void onPackageIconChanged() { } @Override public void onPackageSizeChanged(String packageName) { } @Override public void onAllSizesComputed() { } @Override public void onLauncherInfoChanged() { } @Override public void onLoadEntriesCompleted() { triggerUpdateAppsBypassingDndSummaryText(); } }; } src/com/android/settings/notification/modes/ZenModeFragment.java +5 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,13 @@ package com.android.settings.notification.modes; import android.app.Application; import android.app.AutomaticZenRule; import android.app.settings.SettingsEnums; import android.content.Context; import com.android.settings.R; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; Loading @@ -42,7 +44,9 @@ public class ZenModeFragment extends ZenModeFragmentBase { prefControllers.add(new ZenModePeopleLinkPreferenceController( context, "zen_mode_people", mBackend)); prefControllers.add(new ZenModeAppsLinkPreferenceController( context, "zen_mode_apps", mBackend)); context, "zen_mode_apps", this, ApplicationsState.getInstance((Application) context.getApplicationContext()), mBackend)); prefControllers.add(new ZenModeOtherLinkPreferenceController( context, "zen_other_settings", mBackend)); prefControllers.add(new ZenModeDisplayLinkPreferenceController( Loading src/com/android/settings/notification/modes/ZenModeSummaryHelper.java +41 −4 Original line number Diff line number Diff line Loading @@ -43,13 +43,18 @@ import android.icu.text.MessageFormat; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenPolicy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.R; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Predicate; class ZenModeSummaryHelper { Loading Loading @@ -397,12 +402,13 @@ class ZenModeSummaryHelper { } /** * Generates a summary to display under the top level "Apps" preference for a mode. * Generates a summary to display under the top level "Apps" preference for a mode, based * on the given mode and provided set of apps. */ public String getAppsSummary(ZenMode zenMode) { // TODO: b/308819928 - Set summary using priority app list if Selected Apps Chosen. public @NonNull String getAppsSummary(@NonNull ZenMode zenMode, @Nullable Set<String> appsBypassing) { if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_PRIORITY) { return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps); return formatAppsList(appsBypassing); } else if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) { return mContext.getResources().getString(R.string.zen_mode_apps_none_apps); } else if (zenMode.getPolicy().getAllowedChannels() == ZenMode.CHANNEL_POLICY_ALL) { Loading @@ -410,4 +416,35 @@ class ZenModeSummaryHelper { } return ""; } /** * Generates a formatted string declaring which apps can interrupt in the style of * "App, App2, and 4 more can interrupt." * Apps selected for explicit mention are selected in order from the provided set sorted * alphabetically. */ public @NonNull String formatAppsList(@Nullable Set<String> appsBypassingDnd) { if (appsBypassingDnd == null) { return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps); } final int numAppsBypassingDnd = appsBypassingDnd.size(); String[] appsBypassingDndArr = appsBypassingDnd.toArray(new String[numAppsBypassingDnd]); // Sorts the provided apps alphabetically. Arrays.sort(appsBypassingDndArr); MessageFormat msgFormat = new MessageFormat( mContext.getString(R.string.zen_mode_apps_subtext), Locale.getDefault()); Map<String, Object> args = new HashMap<>(); args.put("count", numAppsBypassingDnd); if (numAppsBypassingDnd >= 1) { args.put("app_1", appsBypassingDndArr[0]); if (numAppsBypassingDnd >= 2) { args.put("app_2", appsBypassingDndArr[1]); if (numAppsBypassingDnd == 3) { args.put("app_3", appsBypassingDndArr[2]); } } } return msgFormat.format(args); } } Loading
res/values/strings.xml +10 −0 Original line number Diff line number Diff line Loading @@ -9214,6 +9214,16 @@ <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=NONE] Zen mode settings: Lists apps that can bypass DND. For example, "Nest, Messages, and 2 more can interrupt". --> <string name="zen_mode_apps_subtext"> {count, plural, offset:2 =0 {No apps can interrupt} =1 {{app_1} can interrupt} =2 {{app_1} and {app_2} can interrupt} =3 {{app_1}, {app_2}, and {app_3} can interrupt} other {{app_1}, {app_2}, and # more can interrupt} } </string> <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND --> <string name="zen_mode_bypassing_apps">Allow apps to override</string>
src/com/android/settings/notification/NotificationBackend.java +13 −0 Original line number Diff line number Diff line Loading @@ -357,6 +357,19 @@ public class NotificationBackend { } } /** * Returns all of a user's packages that have at least one channel that will bypass DND */ public List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels) { try { return sINM.getPackagesBypassingDnd(userId, includeConversationChannels); } catch (Exception e) { Log.w(TAG, "Error calling NoMan", e); return new ArrayList<>(); } } public void updateChannel(String pkg, int uid, NotificationChannel channel) { try { sINM.updateNotificationChannelForPackage(pkg, uid, channel); Loading
src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +103 −4 Original line number Diff line number Diff line Loading @@ -20,23 +20,44 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I import android.content.Context; import android.os.Bundle; import android.util.ArraySet; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; import androidx.fragment.app.Fragment; import androidx.preference.Preference; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.applications.ApplicationsState; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Preference with a link and summary about what apps can break through the mode */ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController { class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController { private static final String TAG = "ZenModeAppsLinkPreferenceController"; private final ZenModeSummaryHelper mSummaryHelper; private ApplicationsState.Session mAppSession; private NotificationBackend mNotificationBackend = new NotificationBackend(); private ZenMode mZenMode; private Preference mPreference; public ZenModeAppsLinkPreferenceController(Context context, String key, ZenModesBackend backend) { ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host, ApplicationsState applicationsState, ZenModesBackend backend) { super(context, key, backend); mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend); if (applicationsState != null && host != null) { mAppSession = applicationsState.newSession(mAppSessionCallbacks, host.getLifecycle()); } } @Override Loading @@ -49,6 +70,84 @@ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferen .setSourceMetricsCategory(0) .setArguments(bundle) .toIntent()); preference.setSummary(mSummaryHelper.getAppsSummary(zenMode)); mZenMode = zenMode; mPreference = preference; triggerUpdateAppsBypassingDndSummaryText(); } private void triggerUpdateAppsBypassingDndSummaryText() { if (mAppSession == null) { return; } ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures() && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() ? ApplicationsState.FILTER_ENABLED_NOT_QUIET : ApplicationsState.FILTER_ALL_ENABLED; // We initiate a rebuild in the background here. Once the rebuild is completed, // the onRebuildComplete() callback will be invoked, which will trigger the summary text // to be initialized. mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR, false); } private void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) { Set<String> appNames = getAppsBypassingDnd(apps); mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, appNames)); } @VisibleForTesting ArraySet<String> getAppsBypassingDnd(@NonNull List<ApplicationsState.AppEntry> apps) { ArraySet<String> appsBypassingDnd = new ArraySet<>(); Map<String, String> pkgLabelMap = new HashMap<String, String>(); for (ApplicationsState.AppEntry entry : apps) { if (entry.info != null) { pkgLabelMap.put(entry.info.packageName, entry.label); } } for (String pkg : mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(), /* includeConversationChannels= */ false)) { // Settings may hide some packages from the user, so if they're not present here // we skip displaying them, even if they bypass dnd. if (pkgLabelMap.get(pkg) == null) { continue; } appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(pkgLabelMap.get(pkg))); } return appsBypassingDnd; } @VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks = new ApplicationsState.Callbacks() { @Override public void onRunningStateChanged(boolean running) { } @Override public void onPackageListChanged() { triggerUpdateAppsBypassingDndSummaryText(); } @Override public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { updateAppsBypassingDndSummaryText(apps); } @Override public void onPackageIconChanged() { } @Override public void onPackageSizeChanged(String packageName) { } @Override public void onAllSizesComputed() { } @Override public void onLauncherInfoChanged() { } @Override public void onLoadEntriesCompleted() { triggerUpdateAppsBypassingDndSummaryText(); } }; }
src/com/android/settings/notification/modes/ZenModeFragment.java +5 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,13 @@ package com.android.settings.notification.modes; import android.app.Application; import android.app.AutomaticZenRule; import android.app.settings.SettingsEnums; import android.content.Context; import com.android.settings.R; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; Loading @@ -42,7 +44,9 @@ public class ZenModeFragment extends ZenModeFragmentBase { prefControllers.add(new ZenModePeopleLinkPreferenceController( context, "zen_mode_people", mBackend)); prefControllers.add(new ZenModeAppsLinkPreferenceController( context, "zen_mode_apps", mBackend)); context, "zen_mode_apps", this, ApplicationsState.getInstance((Application) context.getApplicationContext()), mBackend)); prefControllers.add(new ZenModeOtherLinkPreferenceController( context, "zen_other_settings", mBackend)); prefControllers.add(new ZenModeDisplayLinkPreferenceController( Loading
src/com/android/settings/notification/modes/ZenModeSummaryHelper.java +41 −4 Original line number Diff line number Diff line Loading @@ -43,13 +43,18 @@ import android.icu.text.MessageFormat; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenPolicy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.R; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Predicate; class ZenModeSummaryHelper { Loading Loading @@ -397,12 +402,13 @@ class ZenModeSummaryHelper { } /** * Generates a summary to display under the top level "Apps" preference for a mode. * Generates a summary to display under the top level "Apps" preference for a mode, based * on the given mode and provided set of apps. */ public String getAppsSummary(ZenMode zenMode) { // TODO: b/308819928 - Set summary using priority app list if Selected Apps Chosen. public @NonNull String getAppsSummary(@NonNull ZenMode zenMode, @Nullable Set<String> appsBypassing) { if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_PRIORITY) { return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps); return formatAppsList(appsBypassing); } else if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) { return mContext.getResources().getString(R.string.zen_mode_apps_none_apps); } else if (zenMode.getPolicy().getAllowedChannels() == ZenMode.CHANNEL_POLICY_ALL) { Loading @@ -410,4 +416,35 @@ class ZenModeSummaryHelper { } return ""; } /** * Generates a formatted string declaring which apps can interrupt in the style of * "App, App2, and 4 more can interrupt." * Apps selected for explicit mention are selected in order from the provided set sorted * alphabetically. */ public @NonNull String formatAppsList(@Nullable Set<String> appsBypassingDnd) { if (appsBypassingDnd == null) { return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps); } final int numAppsBypassingDnd = appsBypassingDnd.size(); String[] appsBypassingDndArr = appsBypassingDnd.toArray(new String[numAppsBypassingDnd]); // Sorts the provided apps alphabetically. Arrays.sort(appsBypassingDndArr); MessageFormat msgFormat = new MessageFormat( mContext.getString(R.string.zen_mode_apps_subtext), Locale.getDefault()); Map<String, Object> args = new HashMap<>(); args.put("count", numAppsBypassingDnd); if (numAppsBypassingDnd >= 1) { args.put("app_1", appsBypassingDndArr[0]); if (numAppsBypassingDnd >= 2) { args.put("app_2", appsBypassingDndArr[1]); if (numAppsBypassingDnd == 3) { args.put("app_3", appsBypassingDndArr[2]); } } } return msgFormat.format(args); } }