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.graphics.drawable.Drawable; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 26 import androidx.annotation.VisibleForTesting; 27 import androidx.core.text.BidiFormatter; 28 import androidx.fragment.app.Fragment; 29 import androidx.preference.Preference; 30 import androidx.preference.PreferenceCategory; 31 import androidx.preference.PreferenceScreen; 32 33 import com.android.settings.R; 34 import com.android.settings.applications.AppInfoBase; 35 import com.android.settings.core.PreferenceControllerMixin; 36 import com.android.settings.core.SubSettingLauncher; 37 import com.android.settings.notification.NotificationBackend; 38 import com.android.settings.notification.app.AppChannelsBypassingDndSettings; 39 import com.android.settingslib.applications.AppUtils; 40 import com.android.settingslib.applications.ApplicationsState; 41 import com.android.settingslib.core.AbstractPreferenceController; 42 import com.android.settingslib.utils.ThreadUtils; 43 import com.android.settingslib.widget.AppPreference; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 48 /** 49 * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND. 50 */ 51 public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController 52 implements PreferenceControllerMixin { 53 public static final String KEY_NO_APPS = "all_none"; 54 private static final String KEY = "zen_mode_bypassing_apps_list"; 55 56 private final NotificationBackend mNotificationBackend; 57 58 @VisibleForTesting ApplicationsState mApplicationsState; 59 @VisibleForTesting PreferenceCategory mPreferenceCategory; 60 @VisibleForTesting Context mPrefContext; 61 62 private ApplicationsState.Session mAppSession; 63 private Fragment mHostFragment; 64 ZenModeAllBypassingAppsPreferenceController(Context context, Application app, Fragment host, NotificationBackend notificationBackend)65 public ZenModeAllBypassingAppsPreferenceController(Context context, Application app, 66 Fragment host, NotificationBackend notificationBackend) { 67 this(context, app == null ? null : ApplicationsState.getInstance(app), host, 68 notificationBackend); 69 } 70 ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState, Fragment host, NotificationBackend notificationBackend)71 private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState, 72 Fragment host, NotificationBackend notificationBackend) { 73 super(context); 74 mNotificationBackend = notificationBackend; 75 mApplicationsState = appState; 76 mHostFragment = host; 77 78 if (mApplicationsState != null && host != null) { 79 mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle()); 80 } 81 } 82 83 @Override displayPreference(PreferenceScreen screen)84 public void displayPreference(PreferenceScreen screen) { 85 mPreferenceCategory = screen.findPreference(KEY); 86 mPrefContext = screen.getContext(); 87 updateAppList(); 88 super.displayPreference(screen); 89 } 90 91 @Override isAvailable()92 public boolean isAvailable() { 93 return true; 94 } 95 96 @Override getPreferenceKey()97 public String getPreferenceKey() { 98 return KEY; 99 } 100 101 /** 102 * Call this method to trigger the app list to refresh. 103 */ updateAppList()104 public void updateAppList() { 105 if (mAppSession == null) { 106 return; 107 } 108 109 ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures() 110 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() 111 ? ApplicationsState.FILTER_ENABLED_NOT_QUIET 112 : ApplicationsState.FILTER_ALL_ENABLED; 113 mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR); 114 } 115 116 // Set the icon for the given preference to the entry icon from cache if available, or look 117 // it up. updateIcon(Preference pref, ApplicationsState.AppEntry entry)118 private void updateIcon(Preference pref, ApplicationsState.AppEntry entry) { 119 synchronized (entry) { 120 final Drawable cachedIcon = AppUtils.getIconFromCache(entry); 121 if (cachedIcon != null && entry.mounted) { 122 pref.setIcon(cachedIcon); 123 } else { 124 ThreadUtils.postOnBackgroundThread(() -> { 125 final Drawable icon = AppUtils.getIcon(mPrefContext, entry); 126 if (icon != null) { 127 ThreadUtils.postOnMainThread(() -> pref.setIcon(icon)); 128 } 129 }); 130 } 131 } 132 } 133 134 @VisibleForTesting updateAppList(List<ApplicationsState.AppEntry> apps)135 void updateAppList(List<ApplicationsState.AppEntry> apps) { 136 if (mPreferenceCategory == null || apps == null) { 137 return; 138 } 139 140 boolean doAnyAppsPassCriteria = false; 141 for (ApplicationsState.AppEntry app : apps) { 142 String pkg = app.info.packageName; 143 final String key = getKey(pkg, app.info.uid); 144 final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid); 145 final int appChannelsBypassingDnd = mNotificationBackend 146 .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size(); 147 if (appChannelsBypassingDnd > 0) { 148 doAnyAppsPassCriteria = true; 149 } 150 151 Preference pref = mPreferenceCategory.findPreference(key); 152 if (pref == null) { 153 if (appChannelsBypassingDnd > 0) { 154 // does not exist but should 155 pref = new AppPreference(mPrefContext); 156 pref.setKey(key); 157 pref.setOnPreferenceClickListener(preference -> { 158 Bundle args = new Bundle(); 159 args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName); 160 args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid); 161 new SubSettingLauncher(mContext) 162 .setDestination(AppChannelsBypassingDndSettings.class.getName()) 163 .setArguments(args) 164 .setUserHandle(UserHandle.getUserHandleForUid(app.info.uid)) 165 .setResultListener(mHostFragment, 0) 166 .setSourceMetricsCategory( 167 SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP) 168 .launch(); 169 return true; 170 }); 171 pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label)); 172 updateIcon(pref, app); 173 if (appChannels > appChannelsBypassingDnd) { 174 pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some); 175 } else { 176 pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all); 177 } 178 mPreferenceCategory.addPreference(pref); 179 } 180 } 181 else if (appChannelsBypassingDnd == 0) { 182 // exists but shouldn't anymore 183 mPreferenceCategory.removePreference(pref); 184 } 185 } 186 187 Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS); 188 if (!doAnyAppsPassCriteria) { 189 if (pref == null) { 190 pref = new Preference(mPrefContext); 191 pref.setKey(KEY_NO_APPS); 192 pref.setTitle(R.string.zen_mode_bypassing_apps_none); 193 } 194 mPreferenceCategory.addPreference(pref); 195 } else if (pref != null) { 196 mPreferenceCategory.removePreference(pref); 197 } 198 } 199 200 /** 201 * Create a unique key to idenfity an AppPreference 202 */ getKey(String pkg, int uid)203 static String getKey(String pkg, int uid) { 204 return "all|" + pkg + "|" + uid; 205 } 206 207 private final ApplicationsState.Callbacks mAppSessionCallbacks = 208 new ApplicationsState.Callbacks() { 209 210 @Override 211 public void onRunningStateChanged(boolean running) { 212 } 213 214 @Override 215 public void onPackageListChanged() { 216 } 217 218 @Override 219 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 220 updateAppList(apps); 221 } 222 223 @Override 224 public void onPackageIconChanged() { 225 } 226 227 @Override 228 public void onPackageSizeChanged(String packageName) { 229 } 230 231 @Override 232 public void onAllSizesComputed() { } 233 234 @Override 235 public void onLauncherInfoChanged() { 236 } 237 238 @Override 239 public void onLoadEntriesCompleted() { 240 updateAppList(); 241 } 242 }; 243 } 244