1 package com.android.settings.notification.zen; 2 3 import android.app.Application; 4 import android.app.NotificationChannel; 5 import android.content.Context; 6 import android.icu.text.MessageFormat; 7 import android.provider.Settings; 8 import android.text.TextUtils; 9 import android.util.ArraySet; 10 11 import androidx.core.text.BidiFormatter; 12 import androidx.fragment.app.Fragment; 13 import androidx.preference.Preference; 14 import androidx.preference.PreferenceScreen; 15 16 import com.android.internal.annotations.VisibleForTesting; 17 import com.android.settings.R; 18 import com.android.settings.core.PreferenceControllerMixin; 19 import com.android.settings.notification.NotificationBackend; 20 import com.android.settingslib.applications.ApplicationsState; 21 import com.android.settingslib.core.lifecycle.Lifecycle; 22 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Locale; 27 import java.util.Map; 28 import java.util.Set; 29 30 /** 31 * Controls the summary for preference found at: 32 * Settings > Sound > Do Not Disturb > Apps 33 */ 34 public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePreferenceController 35 implements PreferenceControllerMixin { 36 37 protected static final String KEY = "zen_mode_behavior_apps"; 38 39 @VisibleForTesting protected Preference mPreference; 40 private ApplicationsState.Session mAppSession; 41 private NotificationBackend mNotificationBackend = new NotificationBackend(); 42 43 private String mSummary; 44 ZenModeBypassingAppsPreferenceController(Context context, Application app, Fragment host, Lifecycle lifecycle)45 public ZenModeBypassingAppsPreferenceController(Context context, Application app, 46 Fragment host, Lifecycle lifecycle) { 47 this(context, app == null ? null : ApplicationsState.getInstance(app), host, lifecycle); 48 } 49 ZenModeBypassingAppsPreferenceController(Context context, ApplicationsState appState, Fragment host, Lifecycle lifecycle)50 private ZenModeBypassingAppsPreferenceController(Context context, ApplicationsState appState, 51 Fragment host, Lifecycle lifecycle) { 52 super(context, KEY, lifecycle); 53 if (appState != null && host != null) { 54 mAppSession = appState.newSession(mAppSessionCallbacks, host.getLifecycle()); 55 } 56 } 57 58 @Override isAvailable()59 public boolean isAvailable() { 60 return true; 61 } 62 63 @Override getPreferenceKey()64 public String getPreferenceKey() { 65 return KEY; 66 } 67 68 @Override displayPreference(PreferenceScreen screen)69 public void displayPreference(PreferenceScreen screen) { 70 mPreference = screen.findPreference(KEY); 71 updateAppsBypassingDndSummaryText(); 72 super.displayPreference(screen); 73 } 74 75 @Override getSummary()76 public String getSummary() { 77 return mSummary; 78 } 79 updateAppsBypassingDndSummaryText()80 private void updateAppsBypassingDndSummaryText() { 81 if (mAppSession == null) { 82 return; 83 } 84 85 ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED; 86 List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter, 87 ApplicationsState.ALPHA_COMPARATOR); 88 updateAppsBypassingDndSummaryText(apps); 89 } 90 91 @VisibleForTesting updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps)92 void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) { 93 switch (getZenMode()) { 94 case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS: 95 case Settings.Global.ZEN_MODE_ALARMS: 96 // users cannot change their DND settings when an app puts the device total 97 // silence or alarms only (both deprecated) modes 98 mPreference.setEnabled(false); 99 mSummary = mContext.getResources().getString( 100 R.string.zen_mode_bypassing_apps_subtext_none); 101 return; 102 default: 103 mPreference.setEnabled(true); 104 } 105 106 if (apps == null) { 107 return; 108 } 109 110 Set<String> appsBypassingDnd = new ArraySet<>(); 111 for (ApplicationsState.AppEntry entry : apps) { 112 String pkg = entry.info.packageName; 113 for (NotificationChannel channel : mNotificationBackend 114 .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) { 115 if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) { 116 // conversation channels that bypass dnd will be shown on the People page 117 continue; 118 } 119 appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(entry.label)); 120 continue; 121 } 122 } 123 124 final int numAppsBypassingDnd = appsBypassingDnd.size(); 125 String[] appsBypassingDndArr = appsBypassingDnd.toArray(new String[numAppsBypassingDnd]); 126 MessageFormat msgFormat = new MessageFormat( 127 mContext.getString(R.string.zen_mode_bypassing_apps_subtext), 128 Locale.getDefault()); 129 Map<String, Object> args = new HashMap<>(); 130 args.put("count", numAppsBypassingDnd); 131 if (numAppsBypassingDnd >= 1) { 132 args.put("app_1", appsBypassingDndArr[0]); 133 if (numAppsBypassingDnd >= 2) { 134 args.put("app_2", appsBypassingDndArr[1]); 135 if (numAppsBypassingDnd == 3) { 136 args.put("app_3", appsBypassingDndArr[2]); 137 } 138 } 139 } 140 141 mSummary = msgFormat.format(args); 142 refreshSummary(mPreference); 143 } 144 145 private final ApplicationsState.Callbacks mAppSessionCallbacks = 146 new ApplicationsState.Callbacks() { 147 148 @Override 149 public void onRunningStateChanged(boolean running) { 150 updateAppsBypassingDndSummaryText(); 151 } 152 153 @Override 154 public void onPackageListChanged() { 155 updateAppsBypassingDndSummaryText(); 156 } 157 158 @Override 159 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 160 updateAppsBypassingDndSummaryText(apps); 161 } 162 163 @Override 164 public void onPackageIconChanged() { } 165 166 @Override 167 public void onPackageSizeChanged(String packageName) { 168 updateAppsBypassingDndSummaryText(); 169 } 170 171 @Override 172 public void onAllSizesComputed() { } 173 174 @Override 175 public void onLauncherInfoChanged() { } 176 177 @Override 178 public void onLoadEntriesCompleted() { 179 updateAppsBypassingDndSummaryText(); 180 } 181 }; 182 } 183