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