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