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 17package com.android.settings.notification; 18 19import static android.app.NotificationManager.IMPORTANCE_LOW; 20import static android.app.NotificationManager.IMPORTANCE_NONE; 21import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 22 23import android.app.Activity; 24import android.app.NotificationChannel; 25import android.app.NotificationChannelGroup; 26import android.app.NotificationManager; 27import android.content.Intent; 28import android.os.AsyncTask; 29import android.os.Bundle; 30import android.provider.Settings; 31import android.support.v7.preference.Preference; 32import android.support.v7.preference.PreferenceCategory; 33import android.text.TextUtils; 34import android.util.ArrayMap; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.widget.Switch; 39 40import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 41import com.android.settings.AppHeader; 42import com.android.settings.R; 43import com.android.settings.Utils; 44import com.android.settings.applications.AppHeaderController; 45import com.android.settings.applications.AppInfoBase; 46import com.android.settings.applications.LayoutPreference; 47import com.android.settings.notification.NotificationBackend.AppRow; 48import com.android.settings.overlay.FeatureFactory; 49import com.android.settings.widget.FooterPreference; 50import com.android.settings.widget.MasterSwitchPreference; 51import com.android.settings.widget.SwitchBar; 52import com.android.settingslib.RestrictedSwitchPreference; 53 54import java.util.ArrayList; 55import java.util.Collections; 56import java.util.Comparator; 57import java.util.List; 58 59/** These settings are per app, so should not be returned in global search results. */ 60public class AppNotificationSettings extends NotificationSettingsBase { 61 private static final String TAG = "AppNotificationSettings"; 62 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 63 64 private static String KEY_GENERAL_CATEGORY = "categories"; 65 private static String KEY_DELETED = "deleted"; 66 67 private List<NotificationChannelGroup> mChannelGroupList; 68 private List<PreferenceCategory> mChannelGroups = new ArrayList(); 69 private FooterPreference mDeletedChannels; 70 71 @Override 72 public int getMetricsCategory() { 73 return MetricsEvent.NOTIFICATION_APP_NOTIFICATION; 74 } 75 76 @Override 77 public void onResume() { 78 super.onResume(); 79 80 if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { 81 Log.w(TAG, "Missing package or uid or packageinfo"); 82 finish(); 83 return; 84 } 85 86 if (getPreferenceScreen() != null) { 87 getPreferenceScreen().removeAll(); 88 mChannelGroups.clear(); 89 mDeletedChannels = null; 90 mShowLegacyChannelConfig = false; 91 } 92 93 addPreferencesFromResource(R.xml.notification_settings); 94 getPreferenceScreen().setOrderingAsAdded(true); 95 setupBlock(); 96 addHeaderPref(); 97 98 mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid); 99 if (mShowLegacyChannelConfig) { 100 mChannel = mBackend.getChannel( 101 mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID); 102 populateDefaultChannelPrefs(); 103 } else { 104 addPreferencesFromResource(R.xml.upgraded_app_notification_settings); 105 setupBadge(); 106 // Load channel settings 107 new AsyncTask<Void, Void, Void>() { 108 @Override 109 protected Void doInBackground(Void... unused) { 110 mChannelGroupList = mBackend.getChannelGroups(mPkg, mUid).getList(); 111 Collections.sort(mChannelGroupList, mChannelGroupComparator); 112 return null; 113 } 114 115 @Override 116 protected void onPostExecute(Void unused) { 117 if (getHost() == null) { 118 return; 119 } 120 populateChannelList(); 121 addAppLinkPref(); 122 } 123 }.execute(); 124 } 125 126 updateDependents(mAppRow.banned); 127 } 128 129 private void addHeaderPref() { 130 ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>(); 131 rows.put(mAppRow.pkg, mAppRow); 132 collectConfigActivities(rows); 133 final Activity activity = getActivity(); 134 final Preference pref = FeatureFactory.getFactory(activity) 135 .getApplicationFeatureProvider(activity) 136 .newAppHeaderController(this /* fragment */, null /* appHeader */) 137 .setIcon(mAppRow.icon) 138 .setLabel(mAppRow.label) 139 .setPackageName(mAppRow.pkg) 140 .setUid(mAppRow.uid) 141 .setButtonActions(AppHeaderController.ActionType.ACTION_NONE, 142 AppHeaderController.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 mDeletedChannels = new FooterPreference(getPrefContext()); 197 mDeletedChannels.setSelectable(false); 198 mDeletedChannels.setTitle(getResources().getQuantityString( 199 R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount)); 200 mDeletedChannels.setEnabled(false); 201 mDeletedChannels.setKey(KEY_DELETED); 202 mDeletedChannels.setOrder(ORDER_LAST); 203 getPreferenceScreen().addPreference(mDeletedChannels); 204 } 205 } 206 207 updateDependents(mAppRow.banned); 208 } 209 210 private void populateSingleChannelPrefs(PreferenceCategory groupCategory, 211 final NotificationChannel channel) { 212 MasterSwitchPreference channelPref = new MasterSwitchPreference( 213 getPrefContext()); 214 channelPref.setSwitchEnabled(mSuspendedAppsAdmin == null 215 && isChannelBlockable(mAppRow.systemApp, channel) 216 && isChannelConfigurable(channel)); 217 channelPref.setKey(channel.getId()); 218 channelPref.setTitle(channel.getName()); 219 channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE); 220 channelPref.setSummary(getImportanceSummary(channel)); 221 Bundle channelArgs = new Bundle(); 222 channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); 223 channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true); 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 return getContext().getString(R.string.notification_importance_default); 347 case NotificationManager.IMPORTANCE_HIGH: 348 case NotificationManager.IMPORTANCE_MAX: 349 default: 350 return getContext().getString(R.string.notification_importance_high); 351 } 352 353 } 354 355 private Comparator<NotificationChannel> mChannelComparator = 356 new Comparator<NotificationChannel>() { 357 358 @Override 359 public int compare(NotificationChannel left, NotificationChannel right) { 360 if (left.isDeleted() != right.isDeleted()) { 361 return Boolean.compare(left.isDeleted(), right.isDeleted()); 362 } 363 return left.getId().compareTo(right.getId()); 364 } 365 }; 366 367 private Comparator<NotificationChannelGroup> mChannelGroupComparator = 368 new Comparator<NotificationChannelGroup>() { 369 370 @Override 371 public int compare(NotificationChannelGroup left, NotificationChannelGroup right) { 372 // Non-grouped channels (in placeholder group with a null id) come last 373 if (left.getId() == null && right.getId() != null) { 374 return 1; 375 } else if (right.getId() == null && left.getId() != null) { 376 return -1; 377 } 378 return left.getId().compareTo(right.getId()); 379 } 380 }; 381} 382