Home | History | Annotate | Download | only in notification
      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_LOW;
     20 import static android.app.NotificationManager.IMPORTANCE_NONE;
     21 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
     22 
     23 import android.app.Activity;
     24 import android.app.NotificationChannel;
     25 import android.app.NotificationChannelGroup;
     26 import android.app.NotificationManager;
     27 import android.content.Intent;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.AsyncTask;
     31 import android.provider.Settings;
     32 import android.support.v7.preference.Preference;
     33 import android.text.TextUtils;
     34 import android.text.BidiFormatter;
     35 import android.text.SpannableStringBuilder;
     36 import android.util.ArrayMap;
     37 import android.util.Log;
     38 import android.view.LayoutInflater;
     39 import android.view.View;
     40 import android.widget.Switch;
     41 
     42 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     43 import com.android.settings.AppHeader;
     44 import com.android.settings.R;
     45 import com.android.settings.RingtonePreference;
     46 import com.android.settings.Utils;
     47 import com.android.settings.applications.AppHeaderController;
     48 import com.android.settings.applications.AppInfoBase;
     49 import com.android.settings.applications.LayoutPreference;
     50 import com.android.settings.overlay.FeatureFactory;
     51 import com.android.settings.widget.FooterPreference;
     52 import com.android.settings.widget.SwitchBar;
     53 import com.android.settingslib.RestrictedSwitchPreference;
     54 
     55 public class ChannelNotificationSettings extends NotificationSettingsBase {
     56     private static final String TAG = "ChannelSettings";
     57 
     58     private static final String KEY_LIGHTS = "lights";
     59     private static final String KEY_VIBRATE = "vibrate";
     60     private static final String KEY_RINGTONE = "ringtone";
     61     private static final String KEY_IMPORTANCE = "importance";
     62 
     63     private Preference mImportance;
     64     private RestrictedSwitchPreference mLights;
     65     private RestrictedSwitchPreference mVibrate;
     66     private NotificationSoundPreference mRingtone;
     67     private FooterPreference mFooter;
     68     private NotificationChannelGroup mChannelGroup;
     69     private AppHeaderController mHeaderPref;
     70 
     71     @Override
     72     public int getMetricsCategory() {
     73         return MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION;
     74     }
     75 
     76     @Override
     77     public void onResume() {
     78         super.onResume();
     79         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) {
     80             Log.w(TAG, "Missing package or uid or packageinfo or channel");
     81             finish();
     82             return;
     83         }
     84 
     85         if (getPreferenceScreen() != null) {
     86             getPreferenceScreen().removeAll();
     87         }
     88         addPreferencesFromResource(R.xml.notification_settings);
     89         setupBlock();
     90         addHeaderPref();
     91         addAppLinkPref();
     92         addFooterPref();
     93 
     94         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
     95             populateDefaultChannelPrefs();
     96             mShowLegacyChannelConfig = true;
     97         } else {
     98             populateUpgradedChannelPrefs();
     99 
    100             if (mChannel.getGroup() != null) {
    101                 // Go look up group name
    102                 new AsyncTask<Void, Void, Void>() {
    103                     @Override
    104                     protected Void doInBackground(Void... unused) {
    105                         if (mChannel.getGroup() != null) {
    106                             mChannelGroup = mBackend.getGroup(mChannel.getGroup(), mPkg, mUid);
    107                         }
    108                         return null;
    109                     }
    110 
    111                     @Override
    112                     protected void onPostExecute(Void unused) {
    113                         if (getHost() == null || mChannelGroup == null) {
    114                             return;
    115                         }
    116                         setChannelGroupLabel(mChannelGroup.getName());
    117                     }
    118                 }.execute();
    119             }
    120         }
    121 
    122         updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
    123     }
    124 
    125     private void populateUpgradedChannelPrefs() {
    126         addPreferencesFromResource(R.xml.upgraded_channel_notification_settings);
    127         setupBadge();
    128         setupPriorityPref(mChannel.canBypassDnd());
    129         setupVisOverridePref(mChannel.getLockscreenVisibility());
    130         setupLights();
    131         setupVibrate();
    132         setupRingtone();
    133         setupImportance();
    134     }
    135 
    136     private void addHeaderPref() {
    137         ArrayMap<String, NotificationBackend.AppRow> rows = new ArrayMap<String, NotificationBackend.AppRow>();
    138         rows.put(mAppRow.pkg, mAppRow);
    139         collectConfigActivities(rows);
    140         final Activity activity = getActivity();
    141         mHeaderPref = FeatureFactory.getFactory(activity)
    142                 .getApplicationFeatureProvider(activity)
    143                 .newAppHeaderController(this /* fragment */, null /* appHeader */);
    144         final Preference pref = mHeaderPref
    145                 .setIcon(mAppRow.icon)
    146                 .setLabel(mChannel.getName())
    147                 .setSummary(mAppRow.label)
    148                 .setPackageName(mAppRow.pkg)
    149                 .setUid(mAppRow.uid)
    150                 .setButtonActions(AppHeaderController.ActionType.ACTION_APP_INFO,
    151                         AppHeaderController.ActionType.ACTION_NOTIF_PREFERENCE)
    152                 .done(activity, getPrefContext());
    153         getPreferenceScreen().addPreference(pref);
    154     }
    155 
    156     private void setChannelGroupLabel(CharSequence groupName) {
    157         final SpannableStringBuilder summary = new SpannableStringBuilder();
    158         BidiFormatter bidi = BidiFormatter.getInstance();
    159         summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
    160         if (groupName != null) {
    161             summary.append(bidi.unicodeWrap(mContext.getText(
    162                     R.string.notification_header_divider_symbol_with_spaces)));
    163             summary.append(bidi.unicodeWrap(groupName.toString()));
    164         }
    165         final Activity activity = getActivity();
    166         mHeaderPref.setSummary(summary.toString());
    167         mHeaderPref.done(activity, getPrefContext());
    168     }
    169 
    170     private void addFooterPref() {
    171         if (!TextUtils.isEmpty(mChannel.getDescription())) {
    172             FooterPreference descPref = new FooterPreference(getPrefContext());
    173             descPref.setOrder(ORDER_LAST);
    174             descPref.setSelectable(false);
    175             descPref.setTitle(mChannel.getDescription());
    176             getPreferenceScreen().addPreference(descPref);
    177         }
    178     }
    179 
    180     protected void setupBadge() {
    181         mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
    182         mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
    183         mBadge.setEnabled(mAppRow.showBadge);
    184         mBadge.setChecked(mChannel.canShowBadge());
    185 
    186         mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
    187             @Override
    188             public boolean onPreferenceChange(Preference preference, Object newValue) {
    189                 final boolean value = (Boolean) newValue;
    190                 mChannel.setShowBadge(value);
    191                 mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
    192                 mBackend.updateChannel(mPkg, mUid, mChannel);
    193                 return true;
    194             }
    195         });
    196     }
    197 
    198     private void setupLights() {
    199         mLights = (RestrictedSwitchPreference) findPreference(KEY_LIGHTS);
    200         mLights.setDisabledByAdmin(mSuspendedAppsAdmin);
    201         mLights.setChecked(mChannel.shouldShowLights());
    202         mLights.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
    203             @Override
    204             public boolean onPreferenceChange(Preference preference, Object newValue) {
    205                 final boolean lights = (Boolean) newValue;
    206                 mChannel.enableLights(lights);
    207                 mChannel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
    208                 mBackend.updateChannel(mPkg, mUid, mChannel);
    209                 return true;
    210             }
    211         });
    212     }
    213 
    214     private void setupVibrate() {
    215         mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
    216         mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin);
    217         mVibrate.setEnabled(!mVibrate.isDisabledByAdmin() && isChannelConfigurable(mChannel));
    218         mVibrate.setChecked(mChannel.shouldVibrate());
    219         mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
    220             @Override
    221             public boolean onPreferenceChange(Preference preference, Object newValue) {
    222                 final boolean vibrate = (Boolean) newValue;
    223                 mChannel.enableVibration(vibrate);
    224                 mChannel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
    225                 mBackend.updateChannel(mPkg, mUid, mChannel);
    226                 return true;
    227             }
    228         });
    229     }
    230 
    231     private void setupRingtone() {
    232         mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE);
    233         mRingtone.setRingtone(mChannel.getSound());
    234         mRingtone.setEnabled(isChannelConfigurable(mChannel));
    235         mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
    236             @Override
    237             public boolean onPreferenceChange(Preference preference, Object newValue) {
    238                 mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes());
    239                 mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
    240                 mBackend.updateChannel(mPkg, mUid, mChannel);
    241                 return false;
    242             }
    243         });
    244     }
    245 
    246     private void setupBlock() {
    247         View switchBarContainer = LayoutInflater.from(
    248                 getPrefContext()).inflate(R.layout.styled_switch_bar, null);
    249         mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
    250         mSwitchBar.show();
    251         mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
    252         mSwitchBar.setChecked(mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
    253         mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() {
    254             @Override
    255             public void onSwitchChanged(Switch switchView, boolean isChecked) {
    256                 int importance = 0;
    257                 if (mShowLegacyChannelConfig) {
    258                     importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE;
    259                     mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED);
    260                 } else {
    261                     importance = isChecked ? IMPORTANCE_LOW : IMPORTANCE_NONE;
    262                     mImportance.setSummary(getImportanceSummary(importance));
    263                 }
    264                 mChannel.setImportance(importance);
    265                 mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
    266                 mBackend.updateChannel(mPkg, mUid, mChannel);
    267                 updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
    268             }
    269         });
    270 
    271         mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
    272         mBlockBar.setOrder(ORDER_FIRST);
    273         mBlockBar.setKey(KEY_BLOCK);
    274         getPreferenceScreen().addPreference(mBlockBar);
    275 
    276         if (!isChannelBlockable(mAppRow.systemApp, mChannel)) {
    277             setVisible(mBlockBar, false);
    278         }
    279 
    280         setupBlockDesc(R.string.channel_notifications_off_desc);
    281     }
    282 
    283     private void setupImportance() {
    284         mImportance = findPreference(KEY_IMPORTANCE);
    285         Bundle channelArgs = new Bundle();
    286         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
    287         channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
    288         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
    289         channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
    290         mImportance.setEnabled(mSuspendedAppsAdmin == null && isChannelConfigurable(mChannel));
    291         // Set up intent to show importance selection only if this setting is enabled.
    292         if (mImportance.isEnabled()) {
    293             Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
    294                     ChannelImportanceSettings.class.getName(),
    295                     channelArgs, null, R.string.notification_importance_title, null,
    296                     false, getMetricsCategory());
    297             mImportance.setIntent(channelIntent);
    298         }
    299         mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
    300     }
    301 
    302     private String getImportanceSummary(int importance) {
    303         String title;
    304         String summary = null;
    305         switch (importance) {
    306             case IMPORTANCE_UNSPECIFIED:
    307                 title = getContext().getString(R.string.notification_importance_unspecified);
    308                 break;
    309             case NotificationManager.IMPORTANCE_MIN:
    310                 title = getContext().getString(R.string.notification_importance_min_title);
    311                 summary = getContext().getString(R.string.notification_importance_min);
    312                 break;
    313             case NotificationManager.IMPORTANCE_LOW:
    314                 title = getContext().getString(R.string.notification_importance_low_title);
    315                 summary = getContext().getString(R.string.notification_importance_low);
    316                 break;
    317             case NotificationManager.IMPORTANCE_DEFAULT:
    318                 title = getContext().getString(R.string.notification_importance_default_title);
    319                 if (hasValidSound()) {
    320                     summary = getContext().getString(R.string.notification_importance_default);
    321                 }
    322                 break;
    323             case NotificationManager.IMPORTANCE_HIGH:
    324             case NotificationManager.IMPORTANCE_MAX:
    325                 title = getContext().getString(R.string.notification_importance_high_title);
    326                 if (hasValidSound()) {
    327                     summary = getContext().getString(R.string.notification_importance_high);
    328                 }
    329                 break;
    330             default:
    331                 return "";
    332         }
    333 
    334         if (summary != null) {
    335             return getContext().getString(R.string.notification_importance_divider, title, summary);
    336         } else {
    337             return title;
    338         }
    339     }
    340 
    341     @Override
    342     public boolean onPreferenceTreeClick(Preference preference) {
    343         if (preference instanceof RingtonePreference) {
    344             mRingtone.onPrepareRingtonePickerIntent(mRingtone.getIntent());
    345             startActivityForResult(preference.getIntent(), 200);
    346             return true;
    347         }
    348         return super.onPreferenceTreeClick(preference);
    349     }
    350 
    351     @Override
    352     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    353         if (mRingtone != null) {
    354             mRingtone.onActivityResult(requestCode, resultCode, data);
    355         }
    356         mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
    357     }
    358 
    359     boolean canPulseLight() {
    360         if (!getResources()
    361                 .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
    362             return false;
    363         }
    364         return Settings.System.getInt(getContentResolver(),
    365                 Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1;
    366     }
    367 
    368     boolean hasValidSound() {
    369         return mChannel.getSound() != null && !Uri.EMPTY.equals(mChannel.getSound());
    370     }
    371 
    372     void updateDependents(boolean banned) {
    373         if (mShowLegacyChannelConfig) {
    374             setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
    375         } else {
    376             setVisible(mImportance, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
    377             setVisible(mLights, checkCanBeVisible(
    378                     NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight());
    379             setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
    380             setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
    381         }
    382         setVisible(mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
    383         setVisible(mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
    384                 || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
    385                 && mDndVisualEffectsSuppressed));
    386         setVisible(mVisibilityOverride, checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
    387                 && isLockScreenSecure());
    388         setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE);
    389         if (mAppLink != null) {
    390             setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
    391         }
    392         if (mFooter != null) {
    393             setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
    394         }
    395     }
    396 }
    397