• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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;
18 
19 import android.app.Activity;
20 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
21 
22 import android.app.NotificationChannel;
23 import android.app.NotificationChannelGroup;
24 import android.app.NotificationManager;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.os.Bundle;
29 import android.provider.Settings;
30 import android.support.v7.preference.Preference;
31 import android.support.v7.preference.PreferenceCategory;
32 import android.text.TextUtils;
33 import android.util.ArrayMap;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.widget.Switch;
38 import android.view.Window;
39 import android.view.WindowManager;
40 
41 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
42 import com.android.settings.R;
43 import com.android.settings.Utils;
44 import com.android.settings.applications.AppInfoBase;
45 import com.android.settings.applications.LayoutPreference;
46 import com.android.settings.notification.NotificationBackend.AppRow;
47 import com.android.settings.widget.EntityHeaderController;
48 import com.android.settings.widget.MasterSwitchPreference;
49 import com.android.settings.widget.SwitchBar;
50 import com.android.settingslib.RestrictedSwitchPreference;
51 import com.android.settingslib.widget.FooterPreference;
52 
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
57 
58 import static android.app.NotificationManager.IMPORTANCE_LOW;
59 import static android.app.NotificationManager.IMPORTANCE_NONE;
60 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
61 
62 /** These settings are per app, so should not be returned in global search results. */
63 public class AppNotificationSettings extends NotificationSettingsBase {
64     private static final String TAG = "AppNotificationSettings";
65     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
66 
67     private static String KEY_GENERAL_CATEGORY = "categories";
68     private static String KEY_DELETED = "deleted";
69 
70     private List<NotificationChannelGroup> mChannelGroupList;
71     private List<PreferenceCategory> mChannelGroups = new ArrayList();
72     private FooterPreference mDeletedChannels;
73 
74     @Override
getMetricsCategory()75     public int getMetricsCategory() {
76         return MetricsEvent.NOTIFICATION_APP_NOTIFICATION;
77     }
78 
79     @Override
onResume()80     public void onResume() {
81         super.onResume();
82 
83         getActivity().getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
84         android.util.EventLog.writeEvent(0x534e4554, "119115683", -1, "");
85 
86         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
87             Log.w(TAG, "Missing package or uid or packageinfo");
88             finish();
89             return;
90         }
91 
92         if (getPreferenceScreen() != null) {
93             getPreferenceScreen().removeAll();
94             mChannelGroups.clear();
95             mDeletedChannels = null;
96             mShowLegacyChannelConfig = false;
97         }
98 
99         addPreferencesFromResource(R.xml.notification_settings);
100         getPreferenceScreen().setOrderingAsAdded(true);
101         setupBlock();
102         addHeaderPref();
103 
104         mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid);
105         if (mShowLegacyChannelConfig) {
106             mChannel = mBackend.getChannel(
107                     mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID);
108             populateDefaultChannelPrefs();
109         } else {
110             addPreferencesFromResource(R.xml.upgraded_app_notification_settings);
111             setupBadge();
112             // Load channel settings
113             new AsyncTask<Void, Void, Void>() {
114                 @Override
115                 protected Void doInBackground(Void... unused) {
116                     mChannelGroupList = mBackend.getChannelGroups(mPkg, mUid).getList();
117                     Collections.sort(mChannelGroupList, mChannelGroupComparator);
118                     return null;
119                 }
120 
121                 @Override
122                 protected void onPostExecute(Void unused) {
123                     if (getHost() == null) {
124                         return;
125                     }
126                     populateChannelList();
127                     addAppLinkPref();
128                 }
129             }.execute();
130         }
131 
132         updateDependents(mAppRow.banned);
133     }
134 
135     @Override
onPause()136     public void onPause() {
137         super.onPause();
138         final Window window = getActivity().getWindow();
139         final WindowManager.LayoutParams attrs = window.getAttributes();
140         attrs.privateFlags &= ~PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
141         window.setAttributes(attrs);
142     }
143 
addHeaderPref()144     private void addHeaderPref() {
145         ArrayMap<String, AppRow> rows = new ArrayMap<>();
146         rows.put(mAppRow.pkg, mAppRow);
147         collectConfigActivities(rows);
148         final Activity activity = getActivity();
149         final Preference pref = EntityHeaderController
150                 .newInstance(activity, this /* fragment */, null /* header */)
151                 .setRecyclerView(getListView(), getLifecycle())
152                 .setIcon(mAppRow.icon)
153                 .setLabel(mAppRow.label)
154                 .setPackageName(mAppRow.pkg)
155                 .setUid(mAppRow.uid)
156                 .setHasAppInfoLink(true)
157                 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
158                         EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE)
159                 .done(activity, getPrefContext());
160         pref.setKey(KEY_HEADER);
161         getPreferenceScreen().addPreference(pref);
162     }
163 
populateChannelList()164     private void populateChannelList() {
165         if (!mChannelGroups.isEmpty()) {
166             // If there's anything in mChannelGroups, we've called populateChannelList twice.
167             // Clear out existing channels and log.
168             Log.w(TAG, "Notification channel group posted twice to settings - old size " +
169                     mChannelGroups.size() + ", new size " + mChannelGroupList.size());
170             for (Preference p : mChannelGroups) {
171                 getPreferenceScreen().removePreference(p);
172             }
173         }
174         if (mChannelGroupList.isEmpty()) {
175             PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext());
176             groupCategory.setTitle(R.string.notification_channels);
177             groupCategory.setKey(KEY_GENERAL_CATEGORY);
178             getPreferenceScreen().addPreference(groupCategory);
179             mChannelGroups.add(groupCategory);
180 
181             Preference empty = new Preference(getPrefContext());
182             empty.setTitle(R.string.no_channels);
183             empty.setEnabled(false);
184             groupCategory.addPreference(empty);
185         } else {
186             for (NotificationChannelGroup group : mChannelGroupList) {
187                 PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext());
188                 if (group.getId() == null) {
189                     groupCategory.setTitle(mChannelGroupList.size() > 1
190                             ? R.string.notification_channels_other
191                             : R.string.notification_channels);
192                     groupCategory.setKey(KEY_GENERAL_CATEGORY);
193                 } else {
194                     groupCategory.setTitle(group.getName());
195                     groupCategory.setKey(group.getId());
196                 }
197                 groupCategory.setOrderingAsAdded(true);
198                 getPreferenceScreen().addPreference(groupCategory);
199                 mChannelGroups.add(groupCategory);
200 
201                 final List<NotificationChannel> channels = group.getChannels();
202                 Collections.sort(channels, mChannelComparator);
203                 int N = channels.size();
204                 for (int i = 0; i < N; i++) {
205                     final NotificationChannel channel = channels.get(i);
206                     populateSingleChannelPrefs(groupCategory, channel);
207                 }
208             }
209 
210             int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid);
211             if (deletedChannelCount > 0 &&
212                     getPreferenceScreen().findPreference(KEY_DELETED) == null) {
213                 mDeletedChannels = new FooterPreference(getPrefContext());
214                 mDeletedChannels.setSelectable(false);
215                 mDeletedChannels.setTitle(getResources().getQuantityString(
216                         R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount));
217                 mDeletedChannels.setEnabled(false);
218                 mDeletedChannels.setKey(KEY_DELETED);
219                 mDeletedChannels.setOrder(ORDER_LAST);
220                 getPreferenceScreen().addPreference(mDeletedChannels);
221             }
222         }
223 
224         updateDependents(mAppRow.banned);
225     }
226 
populateSingleChannelPrefs(PreferenceCategory groupCategory, final NotificationChannel channel)227     private void populateSingleChannelPrefs(PreferenceCategory groupCategory,
228             final NotificationChannel channel) {
229         MasterSwitchPreference channelPref = new MasterSwitchPreference(
230                 getPrefContext());
231         channelPref.setSwitchEnabled(mSuspendedAppsAdmin == null
232                 && isChannelBlockable(mAppRow.systemApp, channel)
233                 && isChannelConfigurable(channel));
234         channelPref.setKey(channel.getId());
235         channelPref.setTitle(channel.getName());
236         channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE);
237         channelPref.setSummary(getImportanceSummary(channel));
238         Bundle channelArgs = new Bundle();
239         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
240         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
241         channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
242         Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
243                 ChannelNotificationSettings.class.getName(),
244                 channelArgs, null, R.string.notification_channel_title, null, false,
245                 getMetricsCategory());
246         channelPref.setIntent(channelIntent);
247 
248         channelPref.setOnPreferenceChangeListener(
249                 new Preference.OnPreferenceChangeListener() {
250                     @Override
251                     public boolean onPreferenceChange(Preference preference,
252                             Object o) {
253                         boolean value = (Boolean) o;
254                         int importance = value ?  IMPORTANCE_LOW : IMPORTANCE_NONE;
255                         channel.setImportance(importance);
256                         channel.lockFields(
257                                 NotificationChannel.USER_LOCKED_IMPORTANCE);
258                         channelPref.setSummary(getImportanceSummary(channel));
259                         mBackend.updateChannel(mPkg, mUid, channel);
260 
261                         return true;
262                     }
263                 });
264         groupCategory.addPreference(channelPref);
265     }
266 
setupBadge()267     void setupBadge() {
268         mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
269         mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
270         if (mChannel == null) {
271             mBadge.setChecked(mAppRow.showBadge);
272         } else {
273             mBadge.setChecked(mChannel.canShowBadge());
274         }
275         mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
276             @Override
277             public boolean onPreferenceChange(Preference preference, Object newValue) {
278                 final boolean value = (Boolean) newValue;
279                 if (mChannel == null) {
280                     mBackend.setShowBadge(mPkg, mUid, value);
281                 } else {
282                     mChannel.setShowBadge(value);
283                     mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
284                     mBackend.updateChannel(mPkg, mUid, mChannel);
285                 }
286                 return true;
287             }
288         });
289     }
290 
setupBlock()291     protected void setupBlock() {
292         View switchBarContainer = LayoutInflater.from(
293                 getPrefContext()).inflate(R.layout.styled_switch_bar, null);
294         mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
295         mSwitchBar.show();
296         mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
297         mSwitchBar.setChecked(!mAppRow.banned);
298         mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() {
299             @Override
300             public void onSwitchChanged(Switch switchView, boolean isChecked) {
301                 if (mShowLegacyChannelConfig && mChannel != null) {
302                     final int importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE;
303                     mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED);
304                     mChannel.setImportance(importance);
305                     mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
306                     mBackend.updateChannel(mPkg, mUid, mChannel);
307                 }
308                 mBackend.setNotificationsEnabledForPackage(mPkgInfo.packageName, mUid, isChecked);
309                 mAppRow.banned = true;
310                 updateDependents(!isChecked);
311             }
312         });
313 
314         mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
315         mBlockBar.setOrder(ORDER_FIRST);
316         mBlockBar.setKey(KEY_BLOCK);
317         getPreferenceScreen().addPreference(mBlockBar);
318 
319         if (mAppRow.systemApp && !mAppRow.banned) {
320             setVisible(mBlockBar, false);
321         }
322 
323         setupBlockDesc(R.string.app_notifications_off_desc);
324     }
325 
updateDependents(boolean banned)326     protected void updateDependents(boolean banned) {
327         for (PreferenceCategory category : mChannelGroups) {
328             setVisible(category, !banned);
329         }
330         if (mDeletedChannels != null) {
331             setVisible(mDeletedChannels, !banned);
332         }
333         setVisible(mBlockedDesc, banned);
334         setVisible(mBadge, !banned);
335         if (mShowLegacyChannelConfig) {
336             setVisible(mImportanceToggle, !banned);
337             setVisible(mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
338                     || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
339                     && mDndVisualEffectsSuppressed));
340             setVisible(mVisibilityOverride, !banned &&
341                     checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && isLockScreenSecure());
342         }
343         if (mAppLink != null) {
344             setVisible(mAppLink, !banned);
345         }
346         if (mAppRow.systemApp && !mAppRow.banned) {
347             setVisible(mBlockBar, false);
348         }
349     }
350 
getImportanceSummary(NotificationChannel channel)351     private String getImportanceSummary(NotificationChannel channel) {
352         switch (channel.getImportance()) {
353             case NotificationManager.IMPORTANCE_UNSPECIFIED:
354                 return getContext().getString(R.string.notification_importance_unspecified);
355             case NotificationManager.IMPORTANCE_NONE:
356                 return getContext().getString(R.string.notification_toggle_off);
357             case NotificationManager.IMPORTANCE_MIN:
358                 return getContext().getString(R.string.notification_importance_min);
359             case NotificationManager.IMPORTANCE_LOW:
360                 return getContext().getString(R.string.notification_importance_low);
361             case NotificationManager.IMPORTANCE_DEFAULT:
362                 if (hasValidSound(channel)) {
363                     return getContext().getString(R.string.notification_importance_default);
364                 } else { // Silent
365                     return getContext().getString(R.string.notification_importance_low);
366                 }
367             case NotificationManager.IMPORTANCE_HIGH:
368             case NotificationManager.IMPORTANCE_MAX:
369             default:
370                 if (hasValidSound(channel)) {
371                     return getContext().getString(R.string.notification_importance_high);
372                 } else { // Silent
373                     return getContext().getString(R.string.notification_importance_high_silent);
374                 }
375         }
376     }
377 
378     private Comparator<NotificationChannel> mChannelComparator =
379             new Comparator<NotificationChannel>() {
380 
381         @Override
382         public int compare(NotificationChannel left, NotificationChannel right) {
383             if (left.isDeleted() != right.isDeleted()) {
384                 return Boolean.compare(left.isDeleted(), right.isDeleted());
385             }
386             return left.getId().compareTo(right.getId());
387         }
388     };
389 
390     private Comparator<NotificationChannelGroup> mChannelGroupComparator =
391             new Comparator<NotificationChannelGroup>() {
392 
393                 @Override
394                 public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
395                     // Non-grouped channels (in placeholder group with a null id) come last
396                     if (left.getId() == null && right.getId() != null) {
397                         return 1;
398                     } else if (right.getId() == null && left.getId() != null) {
399                         return -1;
400                     }
401                     return left.getId().compareTo(right.getId());
402                 }
403             };
404 }
405