1 /* 2 * Copyright (C) 2018 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.AppPreference; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Objects; 45 46 47 /** 48 * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND. 49 */ 50 public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController 51 implements PreferenceControllerMixin { 52 public static final String KEY_NO_APPS = getKey("none"); 53 private static final String KEY = "zen_mode_bypassing_apps_list"; 54 55 private final NotificationBackend mNotificationBackend; 56 57 @VisibleForTesting ApplicationsState mApplicationsState; 58 @VisibleForTesting PreferenceCategory mPreferenceCategory; 59 @VisibleForTesting Context mPrefContext; 60 61 private ApplicationsState.Session mAppSession; 62 private Fragment mHostFragment; 63 ZenModeAllBypassingAppsPreferenceController(Context context, Application app, Fragment host, NotificationBackend notificationBackend)64 public ZenModeAllBypassingAppsPreferenceController(Context context, Application app, 65 Fragment host, NotificationBackend notificationBackend) { 66 this(context, app == null ? null : ApplicationsState.getInstance(app), host, 67 notificationBackend); 68 } 69 ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState, Fragment host, NotificationBackend notificationBackend)70 private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState, 71 Fragment host, NotificationBackend notificationBackend) { 72 super(context); 73 mNotificationBackend = notificationBackend; 74 mApplicationsState = appState; 75 mHostFragment = host; 76 77 if (mApplicationsState != null && host != null) { 78 mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle()); 79 } 80 } 81 82 @Override displayPreference(PreferenceScreen screen)83 public void displayPreference(PreferenceScreen screen) { 84 mPreferenceCategory = screen.findPreference(KEY); 85 mPrefContext = screen.getContext(); 86 updateAppList(); 87 super.displayPreference(screen); 88 } 89 90 @Override isAvailable()91 public boolean isAvailable() { 92 return true; 93 } 94 95 @Override getPreferenceKey()96 public String getPreferenceKey() { 97 return KEY; 98 } 99 100 /** 101 * Call this method to trigger the app list to refresh. 102 */ updateAppList()103 public void updateAppList() { 104 if (mAppSession == null) { 105 return; 106 } 107 108 ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED; 109 List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter, 110 ApplicationsState.ALPHA_COMPARATOR); 111 updateAppList(apps); 112 } 113 114 @VisibleForTesting updateAppList(List<ApplicationsState.AppEntry> apps)115 void updateAppList(List<ApplicationsState.AppEntry> apps) { 116 if (mPreferenceCategory == null || apps == null) { 117 return; 118 } 119 120 List<Preference> appsBypassingDnd = new ArrayList<>(); 121 for (ApplicationsState.AppEntry app : apps) { 122 String pkg = app.info.packageName; 123 mApplicationsState.ensureIcon(app); 124 final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid); 125 final int appChannelsBypassingDnd = mNotificationBackend 126 .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size(); 127 if (appChannelsBypassingDnd > 0) { 128 final String key = getKey(pkg); 129 // re-use previously created preference when possible 130 Preference pref = mPreferenceCategory.findPreference(key); 131 if (pref == null) { 132 pref = new AppPreference(mPrefContext); 133 pref.setKey(key); 134 pref.setOnPreferenceClickListener(preference -> { 135 Bundle args = new Bundle(); 136 args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName); 137 args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid); 138 new SubSettingLauncher(mContext) 139 .setDestination(AppChannelsBypassingDndSettings.class.getName()) 140 .setArguments(args) 141 .setUserHandle(UserHandle.getUserHandleForUid(app.info.uid)) 142 .setResultListener(mHostFragment, 0) 143 .setSourceMetricsCategory( 144 SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP) 145 .launch(); 146 return true; 147 }); 148 } 149 pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label)); 150 pref.setIcon(app.icon); 151 if (appChannels > appChannelsBypassingDnd) { 152 pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some); 153 } else { 154 pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all); 155 } 156 157 appsBypassingDnd.add(pref); 158 } 159 } 160 161 if (appsBypassingDnd.size() == 0) { 162 Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS); 163 if (pref == null) { 164 pref = new Preference(mPrefContext); 165 pref.setKey(KEY_NO_APPS); 166 pref.setTitle(R.string.zen_mode_bypassing_apps_none); 167 } 168 appsBypassingDnd.add(pref); 169 } 170 171 if (hasAppListChanged(appsBypassingDnd, mPreferenceCategory)) { 172 mPreferenceCategory.removeAll(); 173 for (Preference prefToAdd : appsBypassingDnd) { 174 mPreferenceCategory.addPreference(prefToAdd); 175 } 176 } 177 } 178 hasAppListChanged(List<Preference> newAppPreferences, PreferenceCategory preferenceCategory)179 static boolean hasAppListChanged(List<Preference> newAppPreferences, 180 PreferenceCategory preferenceCategory) { 181 if (newAppPreferences.size() != preferenceCategory.getPreferenceCount()) { 182 return true; 183 } 184 185 for (int i = 0; i < newAppPreferences.size(); i++) { 186 Preference newAppPref = newAppPreferences.get(i); 187 Preference pref = preferenceCategory.getPreference(i); 188 if (!Objects.equals(newAppPref.getKey(), pref.getKey())) { 189 return true; 190 } 191 } 192 return false; 193 194 } 195 196 /** 197 * Create a unique key to idenfity an AppPreference 198 */ getKey(String pkg)199 static String getKey(String pkg) { 200 return pkg; 201 } 202 203 private final ApplicationsState.Callbacks mAppSessionCallbacks = 204 new ApplicationsState.Callbacks() { 205 206 @Override 207 public void onRunningStateChanged(boolean running) { 208 updateAppList(); 209 } 210 211 @Override 212 public void onPackageListChanged() { 213 updateAppList(); 214 } 215 216 @Override 217 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 218 updateAppList(apps); 219 } 220 221 @Override 222 public void onPackageIconChanged() { 223 updateAppList(); 224 } 225 226 @Override 227 public void onPackageSizeChanged(String packageName) { 228 updateAppList(); 229 } 230 231 @Override 232 public void onAllSizesComputed() { } 233 234 @Override 235 public void onLauncherInfoChanged() { 236 updateAppList(); 237 } 238 239 @Override 240 public void onLoadEntriesCompleted() { 241 updateAppList(); 242 } 243 }; 244 } 245