• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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