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