• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20 import static android.app.NotificationManager.IMPORTANCE_LOW;
21 import static android.app.NotificationManager.IMPORTANCE_NONE;
22 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
23 
24 import com.android.internal.widget.LockPatternUtils;
25 import com.android.settings.R;
26 import com.android.settings.SettingsPreferenceFragment;
27 import com.android.settings.applications.AppInfoBase;
28 import com.android.settings.applications.LayoutPreference;
29 import com.android.settings.widget.SwitchBar;
30 import com.android.settingslib.RestrictedLockUtils;
31 import com.android.settingslib.RestrictedSwitchPreference;
32 import com.android.settingslib.widget.FooterPreference;
33 
34 import android.app.Notification;
35 import android.app.NotificationChannel;
36 import android.app.NotificationManager;
37 import android.app.admin.DevicePolicyManager;
38 import android.content.BroadcastReceiver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.pm.ActivityInfo;
43 import android.content.pm.ApplicationInfo;
44 import android.content.pm.PackageInfo;
45 import android.content.pm.PackageManager;
46 import android.content.pm.PackageManager.NameNotFoundException;
47 import android.content.pm.ResolveInfo;
48 import android.content.pm.UserInfo;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.UserHandle;
52 import android.os.UserManager;
53 import android.provider.Settings;
54 import android.service.notification.NotificationListenerService;
55 import android.support.v7.preference.DropDownPreference;
56 import android.support.v7.preference.Preference;
57 import android.support.v7.preference.PreferenceGroup;
58 import android.text.TextUtils;
59 import android.util.ArrayMap;
60 import android.util.Log;
61 import android.widget.Toast;
62 
63 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
64 
65 import java.util.ArrayList;
66 import java.util.List;
67 
68 abstract public class NotificationSettingsBase extends SettingsPreferenceFragment {
69     private static final String TAG = "NotifiSettingsBase";
70     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
71 
72     private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT
73             = new Intent(Intent.ACTION_MAIN)
74             .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES);
75 
76     protected static final int ORDER_FIRST = -500;
77     protected static final int ORDER_LAST = 1000;
78 
79     protected static final String KEY_APP_LINK = "app_link";
80     protected static final String KEY_HEADER = "header";
81     protected static final String KEY_BLOCK = "block";
82     protected static final String KEY_BADGE = "badge";
83     protected static final String KEY_BYPASS_DND = "bypass_dnd";
84     protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
85     protected static final String KEY_BLOCKED_DESC = "block_desc";
86     protected static final String KEY_ALLOW_SOUND = "allow_sound";
87 
88     protected PackageManager mPm;
89     protected UserManager mUm;
90     protected NotificationBackend mBackend = new NotificationBackend();
91     protected LockPatternUtils mLockPatternUtils;
92     protected NotificationManager mNm;
93     protected Context mContext;
94     protected boolean mCreated;
95     protected int mUid;
96     protected int mUserId;
97     protected String mPkg;
98     protected PackageInfo mPkgInfo;
99     protected RestrictedSwitchPreference mBadge;
100     protected RestrictedSwitchPreference mPriority;
101     protected RestrictedDropDownPreference mVisibilityOverride;
102     protected RestrictedSwitchPreference mImportanceToggle;
103     protected LayoutPreference mBlockBar;
104     protected SwitchBar mSwitchBar;
105     protected FooterPreference mBlockedDesc;
106     protected Preference mAppLink;
107 
108     protected EnforcedAdmin mSuspendedAppsAdmin;
109     protected boolean mDndVisualEffectsSuppressed;
110 
111     protected NotificationChannel mChannel;
112     protected NotificationBackend.AppRow mAppRow;
113     protected boolean mShowLegacyChannelConfig = false;
114 
115     protected boolean mListeningToPackageRemove;
116 
117     @Override
onActivityCreated(Bundle savedInstanceState)118     public void onActivityCreated(Bundle savedInstanceState) {
119         super.onActivityCreated(savedInstanceState);
120         if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated);
121         if (mCreated) {
122             Log.w(TAG, "onActivityCreated: ignoring duplicate call");
123             return;
124         }
125         mCreated = true;
126     }
127 
128     @Override
onCreate(Bundle savedInstanceState)129     public void onCreate(Bundle savedInstanceState) {
130         super.onCreate(savedInstanceState);
131         mContext = getActivity();
132         Intent intent = getActivity().getIntent();
133         Bundle args = getArguments();
134         if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent);
135         if (intent == null && args == null) {
136             Log.w(TAG, "No intent");
137             toastAndFinish();
138             return;
139         }
140 
141         mPm = getPackageManager();
142         mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
143         mNm = NotificationManager.from(mContext);
144 
145         mPkg = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_NAME)
146                 ? args.getString(AppInfoBase.ARG_PACKAGE_NAME)
147                 : intent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
148         mUid = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_UID)
149                 ? args.getInt(AppInfoBase.ARG_PACKAGE_UID)
150                 : intent.getIntExtra(Settings.EXTRA_APP_UID, -1);
151 
152         if (mUid < 0) {
153             try {
154                 mUid = mPm.getPackageUid(mPkg, 0);
155             } catch (NameNotFoundException e) {
156             }
157         }
158 
159         mPkgInfo = findPackageInfo(mPkg, mUid);
160 
161         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
162             Log.w(TAG, "Missing package or uid or packageinfo");
163             toastAndFinish();
164             return;
165         }
166 
167         mUserId = UserHandle.getUserId(mUid);
168         startListeningToPackageRemove();
169     }
170 
171     @Override
onDestroy()172     public void onDestroy() {
173         stopListeningToPackageRemove();
174         super.onDestroy();
175     }
176 
177     @Override
onResume()178     public void onResume() {
179         super.onResume();
180         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
181             Log.w(TAG, "Missing package or uid or packageinfo");
182             finish();
183             return;
184         }
185         mAppRow = mBackend.loadAppRow(mContext, mPm, mPkgInfo);
186         Bundle args = getArguments();
187         mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ?
188                 mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null;
189 
190         mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
191                 mContext, mPkg, mUserId);
192         NotificationManager.Policy policy = mNm.getNotificationPolicy();
193         mDndVisualEffectsSuppressed = policy == null ? false : policy.suppressedVisualEffects != 0;
194 
195         mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
196                 mContext, mPkg, mUserId);
197     }
198 
setVisible(Preference p, boolean visible)199     protected void setVisible(Preference p, boolean visible) {
200         setVisible(getPreferenceScreen(), p, visible);
201     }
202 
setVisible(PreferenceGroup parent, Preference p, boolean visible)203     protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) {
204         final boolean isVisible = parent.findPreference(p.getKey()) != null;
205         if (isVisible == visible) return;
206         if (visible) {
207             parent.addPreference(p);
208         } else {
209             parent.removePreference(p);
210         }
211     }
212 
toastAndFinish()213     protected void toastAndFinish() {
214         Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
215         getActivity().finish();
216     }
217 
queryNotificationConfigActivities()218     private List<ResolveInfo> queryNotificationConfigActivities() {
219         if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is "
220                 + APP_NOTIFICATION_PREFS_CATEGORY_INTENT);
221         final List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(
222                 APP_NOTIFICATION_PREFS_CATEGORY_INTENT,
223                 0 //PackageManager.MATCH_DEFAULT_ONLY
224         );
225         return resolveInfos;
226     }
227 
collectConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows)228     protected void collectConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows) {
229         final List<ResolveInfo> resolveInfos = queryNotificationConfigActivities();
230         applyConfigActivities(rows, resolveInfos);
231     }
232 
applyConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows, List<ResolveInfo> resolveInfos)233     private void applyConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows,
234             List<ResolveInfo> resolveInfos) {
235         if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"
236                 + (resolveInfos.size() == 0 ? " ;_;" : ""));
237         for (ResolveInfo ri : resolveInfos) {
238             final ActivityInfo activityInfo = ri.activityInfo;
239             final ApplicationInfo appInfo = activityInfo.applicationInfo;
240             final NotificationBackend.AppRow row = rows.get(appInfo.packageName);
241             if (row == null) {
242                 if (DEBUG) Log.v(TAG, "Ignoring notification preference activity ("
243                         + activityInfo.name + ") for unknown package "
244                         + activityInfo.packageName);
245                 continue;
246             }
247             if (row.settingsIntent != null) {
248                 if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity ("
249                         + activityInfo.name + ") for package "
250                         + activityInfo.packageName);
251                 continue;
252             }
253             row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT)
254                     .setClassName(activityInfo.packageName, activityInfo.name);
255             if (mChannel != null) {
256                 row.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId());
257             }
258         }
259     }
260 
findPackageInfo(String pkg, int uid)261     private PackageInfo findPackageInfo(String pkg, int uid) {
262         if (pkg == null || uid < 0) {
263             return null;
264         }
265         final String[] packages = mPm.getPackagesForUid(uid);
266         if (packages != null && pkg != null) {
267             final int N = packages.length;
268             for (int i = 0; i < N; i++) {
269                 final String p = packages[i];
270                 if (pkg.equals(p)) {
271                     try {
272                         return mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
273                     } catch (NameNotFoundException e) {
274                         Log.w(TAG, "Failed to load package " + pkg, e);
275                     }
276                 }
277             }
278         }
279         return null;
280     }
281 
addAppLinkPref()282     protected void addAppLinkPref() {
283         if (mAppRow.settingsIntent != null && mAppLink == null) {
284             addPreferencesFromResource(R.xml.inapp_notification_settings);
285             mAppLink = (Preference) findPreference(KEY_APP_LINK);
286             mAppLink.setIntent(mAppRow.settingsIntent);
287         }
288     }
289 
populateDefaultChannelPrefs()290     protected void populateDefaultChannelPrefs() {
291         if (mPkgInfo != null && mChannel != null) {
292             addPreferencesFromResource(R.xml.legacy_channel_notification_settings);
293             setupPriorityPref(mChannel.canBypassDnd());
294             setupVisOverridePref(mChannel.getLockscreenVisibility());
295             setupImportanceToggle();
296             setupBadge();
297         }
298         mSwitchBar.setChecked(!mAppRow.banned
299                 && mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
300     }
301 
setupBadge()302     abstract void setupBadge();
303 
updateDependents(boolean banned)304     abstract void updateDependents(boolean banned);
305 
306     // 'allow sound'
setupImportanceToggle()307     private void setupImportanceToggle() {
308         mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND);
309         mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin);
310         mImportanceToggle.setEnabled(isChannelConfigurable(mChannel)
311                 && !mImportanceToggle.isDisabledByAdmin());
312         mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT
313                 || mChannel.getImportance() == IMPORTANCE_UNSPECIFIED);
314         mImportanceToggle.setOnPreferenceChangeListener(
315                 new Preference.OnPreferenceChangeListener() {
316                     @Override
317                     public boolean onPreferenceChange(Preference preference, Object newValue) {
318                         final int importance =
319                                 ((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW);
320                         mChannel.setImportance(importance);
321                         mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
322                         mBackend.updateChannel(mPkg, mUid, mChannel);
323                         updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
324                         return true;
325                     }
326                 });
327     }
328 
setupPriorityPref(boolean priority)329     protected void setupPriorityPref(boolean priority) {
330         mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND);
331         mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
332         mPriority.setEnabled(isChannelConfigurable(mChannel) && !mPriority.isDisabledByAdmin());
333         mPriority.setChecked(priority);
334         mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
335             @Override
336             public boolean onPreferenceChange(Preference preference, Object newValue) {
337                 final boolean bypassZenMode = (Boolean) newValue;
338                 mChannel.setBypassDnd(bypassZenMode);
339                 mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
340                 mBackend.updateChannel(mPkg, mUid, mChannel);
341                 return true;
342             }
343         });
344     }
345 
setupVisOverridePref(int sensitive)346     protected void setupVisOverridePref(int sensitive) {
347         mVisibilityOverride =
348                 (RestrictedDropDownPreference) findPreference(KEY_VISIBILITY_OVERRIDE);
349         ArrayList<CharSequence> entries = new ArrayList<>();
350         ArrayList<CharSequence> values = new ArrayList<>();
351 
352         mVisibilityOverride.clearRestrictedItems();
353         if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) {
354             final String summaryShowEntry =
355                     getString(R.string.lock_screen_notifications_summary_show);
356             final String summaryShowEntryValue =
357                     Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE);
358             entries.add(summaryShowEntry);
359             values.add(summaryShowEntryValue);
360             setRestrictedIfNotificationFeaturesDisabled(summaryShowEntry, summaryShowEntryValue,
361                     DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
362                             | DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
363         }
364 
365         final String summaryHideEntry = getString(R.string.lock_screen_notifications_summary_hide);
366         final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE);
367         entries.add(summaryHideEntry);
368         values.add(summaryHideEntryValue);
369         setRestrictedIfNotificationFeaturesDisabled(summaryHideEntry, summaryHideEntryValue,
370                 DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
371         entries.add(getString(R.string.lock_screen_notifications_summary_disable));
372         values.add(Integer.toString(Notification.VISIBILITY_SECRET));
373         mVisibilityOverride.setEntries(entries.toArray(new CharSequence[entries.size()]));
374         mVisibilityOverride.setEntryValues(values.toArray(new CharSequence[values.size()]));
375 
376         if (sensitive == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
377             mVisibilityOverride.setValue(Integer.toString(getGlobalVisibility()));
378         } else {
379             mVisibilityOverride.setValue(Integer.toString(sensitive));
380         }
381         mVisibilityOverride.setSummary("%s");
382 
383         mVisibilityOverride.setOnPreferenceChangeListener(
384                 new Preference.OnPreferenceChangeListener() {
385                     @Override
386                     public boolean onPreferenceChange(Preference preference, Object newValue) {
387                         int sensitive = Integer.parseInt((String) newValue);
388                         if (sensitive == getGlobalVisibility()) {
389                             sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
390                         }
391                         mChannel.setLockscreenVisibility(sensitive);
392                         mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
393                         mBackend.updateChannel(mPkg, mUid, mChannel);
394                         return true;
395                     }
396                 });
397         mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin);
398     }
399 
setupBlockDesc(int summaryResId)400     protected void setupBlockDesc(int summaryResId) {
401         mBlockedDesc = (FooterPreference) getPreferenceScreen().findPreference(
402                 KEY_BLOCKED_DESC);
403         mBlockedDesc = new FooterPreference(getPrefContext());
404         mBlockedDesc.setSelectable(false);
405         mBlockedDesc.setTitle(summaryResId);
406         mBlockedDesc.setEnabled(false);
407         mBlockedDesc.setOrder(50);
408         getPreferenceScreen().addPreference(mBlockedDesc);
409     }
410 
checkCanBeVisible(int minImportanceVisible)411     protected boolean checkCanBeVisible(int minImportanceVisible) {
412         int importance = mChannel.getImportance();
413         if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
414             return true;
415         }
416         return importance >= minImportanceVisible;
417     }
418 
setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures)419     private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry,
420             CharSequence entryValue, int keyguardNotificationFeatures) {
421         RestrictedLockUtils.EnforcedAdmin admin =
422                 RestrictedLockUtils.checkIfKeyguardFeaturesDisabled(
423                         mContext, keyguardNotificationFeatures, mUserId);
424         if (admin != null) {
425             RestrictedDropDownPreference.RestrictedItem item =
426                     new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin);
427             mVisibilityOverride.addRestrictedItem(item);
428         }
429     }
430 
getGlobalVisibility()431     private int getGlobalVisibility() {
432         int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
433         if (!getLockscreenNotificationsEnabled()) {
434             globalVis = Notification.VISIBILITY_SECRET;
435         } else if (!getLockscreenAllowPrivateNotifications()) {
436             globalVis = Notification.VISIBILITY_PRIVATE;
437         }
438         return globalVis;
439     }
440 
getLockscreenNotificationsEnabled()441     private boolean getLockscreenNotificationsEnabled() {
442         return Settings.Secure.getInt(getContentResolver(),
443                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0;
444     }
445 
getLockscreenAllowPrivateNotifications()446     private boolean getLockscreenAllowPrivateNotifications() {
447         return Settings.Secure.getInt(getContentResolver(),
448                 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0;
449     }
450 
isLockScreenSecure()451     protected boolean isLockScreenSecure() {
452         if (mLockPatternUtils == null) {
453             mLockPatternUtils = new LockPatternUtils(getActivity());
454         }
455         boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId());
456         UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId());
457         if (parentUser != null){
458             lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id);
459         }
460 
461         return lockscreenSecure;
462     }
463 
isChannelConfigurable(NotificationChannel channel)464     protected boolean isChannelConfigurable(NotificationChannel channel) {
465         return !channel.getId().equals(mAppRow.lockedChannelId);
466     }
467 
isChannelBlockable(boolean systemApp, NotificationChannel channel)468     protected boolean isChannelBlockable(boolean systemApp, NotificationChannel channel) {
469         if (!mAppRow.systemApp) {
470             return true;
471         }
472 
473         return channel.isBlockableSystem()
474                 || channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
475     }
476 
startListeningToPackageRemove()477     protected void startListeningToPackageRemove() {
478         if (mListeningToPackageRemove) {
479             return;
480         }
481         mListeningToPackageRemove = true;
482         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
483         filter.addDataScheme("package");
484         getContext().registerReceiver(mPackageRemovedReceiver, filter);
485     }
486 
stopListeningToPackageRemove()487     protected void stopListeningToPackageRemove() {
488         if (!mListeningToPackageRemove) {
489             return;
490         }
491         mListeningToPackageRemove = false;
492         getContext().unregisterReceiver(mPackageRemovedReceiver);
493     }
494 
onPackageRemoved()495     protected void onPackageRemoved() {
496         getActivity().finishAndRemoveTask();
497     }
498 
499     protected final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
500         @Override
501         public void onReceive(Context context, Intent intent) {
502             String packageName = intent.getData().getSchemeSpecificPart();
503             if (mPkgInfo == null || TextUtils.equals(mPkgInfo.packageName, packageName)) {
504                 if (DEBUG) Log.d(TAG, "Package (" + packageName + ") removed. Removing"
505                         + "NotificationSettingsBase.");
506                 onPackageRemoved();
507             }
508         }
509     };
510 
hasValidSound(NotificationChannel channel)511     boolean hasValidSound(NotificationChannel channel) {
512         return channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound());
513     }
514 }
515