1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification.zen; 18 19 import android.app.Application; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.os.UserHandle; 24 25 import androidx.annotation.VisibleForTesting; 26 import androidx.core.text.BidiFormatter; 27 import androidx.fragment.app.Fragment; 28 import androidx.preference.Preference; 29 import androidx.preference.PreferenceCategory; 30 import androidx.preference.PreferenceScreen; 31 32 import com.android.settings.R; 33 import com.android.settings.applications.AppInfoBase; 34 import com.android.settings.core.PreferenceControllerMixin; 35 import com.android.settings.core.SubSettingLauncher; 36 import com.android.settings.notification.NotificationBackend; 37 import com.android.settings.notification.app.AppChannelsBypassingDndSettings; 38 import com.android.settingslib.applications.ApplicationsState; 39 import com.android.settingslib.core.AbstractPreferenceController; 40 import com.android.settingslib.widget.AppPreference; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 45 46 /** 47 * When clicked, populates the PreferenceScreen with apps that aren't already bypassing DND. The 48 * user can click on these Preferences to allow notification channels from the app to bypass DND. 49 */ 50 public class ZenModeAddBypassingAppsPreferenceController extends AbstractPreferenceController 51 implements PreferenceControllerMixin { 52 53 private static final String KEY = "zen_mode_non_bypassing_apps_list"; 54 private static final String KEY_ADD = "zen_mode_bypassing_apps_add"; 55 private final NotificationBackend mNotificationBackend; 56 57 @VisibleForTesting ApplicationsState mApplicationsState; 58 @VisibleForTesting PreferenceScreen mPreferenceScreen; 59 @VisibleForTesting PreferenceCategory mPreferenceCategory; 60 @VisibleForTesting Context mPrefContext; 61 62 private Preference mAddPreference; 63 private ApplicationsState.Session mAppSession; 64 private Fragment mHostFragment; 65 ZenModeAddBypassingAppsPreferenceController(Context context, Application app, Fragment host, NotificationBackend notificationBackend)66 public ZenModeAddBypassingAppsPreferenceController(Context context, Application app, 67 Fragment host, NotificationBackend notificationBackend) { 68 this(context, app == null ? null : ApplicationsState.getInstance(app), host, 69 notificationBackend); 70 } 71 ZenModeAddBypassingAppsPreferenceController(Context context, ApplicationsState appState, Fragment host, NotificationBackend notificationBackend)72 private ZenModeAddBypassingAppsPreferenceController(Context context, ApplicationsState appState, 73 Fragment host, NotificationBackend notificationBackend) { 74 super(context); 75 mNotificationBackend = notificationBackend; 76 mApplicationsState = appState; 77 mHostFragment = host; 78 } 79 80 @Override displayPreference(PreferenceScreen screen)81 public void displayPreference(PreferenceScreen screen) { 82 mPreferenceScreen = screen; 83 mAddPreference = screen.findPreference(KEY_ADD); 84 mAddPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 85 @Override 86 public boolean onPreferenceClick(Preference preference) { 87 mAddPreference.setVisible(false); 88 if (mApplicationsState != null && mHostFragment != null) { 89 mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, 90 mHostFragment.getLifecycle()); 91 } 92 return true; 93 } 94 }); 95 mPrefContext = screen.getContext(); 96 super.displayPreference(screen); 97 } 98 99 @Override isAvailable()100 public boolean isAvailable() { 101 return true; 102 } 103 104 @Override getPreferenceKey()105 public String getPreferenceKey() { 106 return KEY; 107 } 108 109 /** 110 * Call this method to trigger the app list to refresh. 111 */ updateAppList()112 public void updateAppList() { 113 if (mAppSession == null) { 114 return; 115 } 116 117 ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED; 118 List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter, 119 ApplicationsState.ALPHA_COMPARATOR); 120 updateAppList(apps); 121 } 122 123 @VisibleForTesting updateAppList(List<ApplicationsState.AppEntry> apps)124 void updateAppList(List<ApplicationsState.AppEntry> apps) { 125 if (apps == null) { 126 return; 127 } 128 129 if (mPreferenceCategory == null) { 130 mPreferenceCategory = new PreferenceCategory(mPrefContext); 131 mPreferenceCategory.setTitle(R.string.zen_mode_bypassing_apps_add_header); 132 mPreferenceScreen.addPreference(mPreferenceCategory); 133 } 134 135 List<Preference> appsWithNoBypassingDndNotificationChannels = new ArrayList<>(); 136 for (ApplicationsState.AppEntry entry : apps) { 137 String pkg = entry.info.packageName; 138 mApplicationsState.ensureIcon(entry); 139 final int appChannels = mNotificationBackend.getChannelCount(pkg, entry.info.uid); 140 final int appChannelsBypassingDnd = mNotificationBackend 141 .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList().size(); 142 if (appChannelsBypassingDnd == 0 && appChannels > 0) { 143 final String key = ZenModeAllBypassingAppsPreferenceController.getKey(pkg); 144 Preference pref = mPreferenceCategory.findPreference(""); 145 if (pref == null) { 146 pref = new AppPreference(mPrefContext); 147 pref.setKey(key); 148 pref.setOnPreferenceClickListener(preference -> { 149 Bundle args = new Bundle(); 150 args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName); 151 args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid); 152 new SubSettingLauncher(mContext) 153 .setDestination(AppChannelsBypassingDndSettings.class.getName()) 154 .setArguments(args) 155 .setResultListener(mHostFragment, 0) 156 .setUserHandle(new UserHandle(UserHandle.getUserId(entry.info.uid))) 157 .setSourceMetricsCategory( 158 SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP) 159 .launch(); 160 return true; 161 }); 162 } 163 pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label)); 164 pref.setIcon(entry.icon); 165 appsWithNoBypassingDndNotificationChannels.add(pref); 166 } 167 } 168 169 if (appsWithNoBypassingDndNotificationChannels.size() == 0) { 170 Preference pref = mPreferenceCategory.findPreference( 171 ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS); 172 if (pref == null) { 173 pref = new Preference(mPrefContext); 174 pref.setKey(ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS); 175 pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none); 176 } 177 mPreferenceCategory.addPreference(pref); 178 } 179 180 if (ZenModeAllBypassingAppsPreferenceController.hasAppListChanged( 181 appsWithNoBypassingDndNotificationChannels, mPreferenceCategory)) { 182 mPreferenceCategory.removeAll(); 183 for (Preference prefToAdd : appsWithNoBypassingDndNotificationChannels) { 184 mPreferenceCategory.addPreference(prefToAdd); 185 } 186 } 187 } 188 189 private final ApplicationsState.Callbacks mAppSessionCallbacks = 190 new ApplicationsState.Callbacks() { 191 192 @Override 193 public void onRunningStateChanged(boolean running) { 194 updateAppList(); 195 } 196 197 @Override 198 public void onPackageListChanged() { 199 updateAppList(); 200 } 201 202 @Override 203 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 204 updateAppList(apps); 205 } 206 207 @Override 208 public void onPackageIconChanged() { 209 updateAppList(); 210 } 211 212 @Override 213 public void onPackageSizeChanged(String packageName) { 214 updateAppList(); 215 } 216 217 @Override 218 public void onAllSizesComputed() { } 219 220 @Override 221 public void onLauncherInfoChanged() { 222 updateAppList(); 223 } 224 225 @Override 226 public void onLoadEntriesCompleted() { 227 updateAppList(); 228 } 229 }; 230 } 231