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