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.NotificationManager; 20 import android.content.BroadcastReceiver; 21 import android.content.ComponentName; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ServiceInfo; 28 import android.database.ContentObserver; 29 import android.database.Cursor; 30 import android.database.sqlite.SQLiteException; 31 import android.media.AudioManager; 32 import android.media.RingtoneManager; 33 import android.net.Uri; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.Vibrator; 40 import android.preference.Preference; 41 import android.preference.Preference.OnPreferenceChangeListener; 42 import android.preference.PreferenceCategory; 43 import android.preference.SeekBarVolumizer; 44 import android.preference.TwoStatePreference; 45 import android.provider.MediaStore; 46 import android.provider.OpenableColumns; 47 import android.provider.SearchIndexableResource; 48 import android.provider.Settings; 49 import android.util.Log; 50 51 import com.android.internal.widget.LockPatternUtils; 52 import com.android.settings.R; 53 import com.android.settings.SettingsPreferenceFragment; 54 import com.android.settings.Utils; 55 import com.android.settings.search.BaseSearchIndexProvider; 56 import com.android.settings.search.Indexable; 57 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.List; 61 import java.util.Objects; 62 63 public class NotificationSettings extends SettingsPreferenceFragment implements Indexable { 64 private static final String TAG = "NotificationSettings"; 65 66 private static final String KEY_SOUND = "sound"; 67 private static final String KEY_MEDIA_VOLUME = "media_volume"; 68 private static final String KEY_ALARM_VOLUME = "alarm_volume"; 69 private static final String KEY_RING_VOLUME = "ring_volume"; 70 private static final String KEY_NOTIFICATION_VOLUME = "notification_volume"; 71 private static final String KEY_PHONE_RINGTONE = "ringtone"; 72 private static final String KEY_NOTIFICATION_RINGTONE = "notification_ringtone"; 73 private static final String KEY_VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; 74 private static final String KEY_NOTIFICATION = "notification"; 75 private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; 76 private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "lock_screen_notifications"; 77 private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access"; 78 79 private static final int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds 80 81 private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); 82 private final H mHandler = new H(); 83 private final SettingsObserver mSettingsObserver = new SettingsObserver(); 84 private final Receiver mReceiver = new Receiver(); 85 private final ArrayList<VolumeSeekBarPreference> mVolumePrefs = new ArrayList<>(); 86 87 private Context mContext; 88 private PackageManager mPM; 89 private boolean mVoiceCapable; 90 private Vibrator mVibrator; 91 private AudioManager mAudioManager; 92 private VolumeSeekBarPreference mRingOrNotificationPreference; 93 94 private Preference mPhoneRingtonePreference; 95 private Preference mNotificationRingtonePreference; 96 private TwoStatePreference mVibrateWhenRinging; 97 private TwoStatePreference mNotificationPulse; 98 private DropDownPreference mLockscreen; 99 private Preference mNotificationAccess; 100 private boolean mSecure; 101 private int mLockscreenSelectedValue; 102 private ComponentName mSuppressor; 103 private int mRingerMode = -1; 104 105 @Override 106 public void onCreate(Bundle savedInstanceState) { 107 super.onCreate(savedInstanceState); 108 mContext = getActivity(); 109 mPM = mContext.getPackageManager(); 110 mVoiceCapable = Utils.isVoiceCapable(mContext); 111 mSecure = new LockPatternUtils(getActivity()).isSecure(); 112 113 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 114 mVibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); 115 if (mVibrator != null && !mVibrator.hasVibrator()) { 116 mVibrator = null; 117 } 118 119 addPreferencesFromResource(R.xml.notification_settings); 120 121 final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND); 122 initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC, 123 com.android.internal.R.drawable.ic_audio_vol_mute); 124 initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM, 125 com.android.internal.R.drawable.ic_audio_alarm_mute); 126 if (mVoiceCapable) { 127 mRingOrNotificationPreference = 128 initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING, 129 com.android.internal.R.drawable.ic_audio_ring_notif_mute); 130 sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME)); 131 } else { 132 mRingOrNotificationPreference = 133 initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION, 134 com.android.internal.R.drawable.ic_audio_ring_notif_mute); 135 sound.removePreference(sound.findPreference(KEY_RING_VOLUME)); 136 } 137 initRingtones(sound); 138 initVibrateWhenRinging(sound); 139 140 final PreferenceCategory notification = (PreferenceCategory) 141 findPreference(KEY_NOTIFICATION); 142 initPulse(notification); 143 initLockscreenNotifications(notification); 144 145 mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS); 146 refreshNotificationListeners(); 147 updateRingerMode(); 148 updateEffectsSuppressor(); 149 } 150 151 @Override 152 public void onResume() { 153 super.onResume(); 154 refreshNotificationListeners(); 155 lookupRingtoneNames(); 156 mSettingsObserver.register(true); 157 mReceiver.register(true); 158 updateRingOrNotificationPreference(); 159 updateEffectsSuppressor(); 160 for (VolumeSeekBarPreference volumePref : mVolumePrefs) { 161 volumePref.onActivityResume(); 162 } 163 } 164 165 @Override 166 public void onPause() { 167 super.onPause(); 168 mVolumeCallback.stopSample(); 169 mSettingsObserver.register(false); 170 mReceiver.register(false); 171 } 172 173 // === Volumes === 174 175 private VolumeSeekBarPreference initVolumePreference(String key, int stream, int muteIcon) { 176 final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key); 177 volumePref.setCallback(mVolumeCallback); 178 volumePref.setStream(stream); 179 mVolumePrefs.add(volumePref); 180 volumePref.setMuteIcon(muteIcon); 181 return volumePref; 182 } 183 184 private void updateRingOrNotificationPreference() { 185 mRingOrNotificationPreference.showIcon(mSuppressor != null 186 ? com.android.internal.R.drawable.ic_audio_ring_notif_mute 187 : mRingerMode == AudioManager.RINGER_MODE_VIBRATE 188 ? com.android.internal.R.drawable.ic_audio_ring_notif_vibrate 189 : com.android.internal.R.drawable.ic_audio_ring_notif); 190 } 191 192 private void updateRingerMode() { 193 final int ringerMode = mAudioManager.getRingerModeInternal(); 194 if (mRingerMode == ringerMode) return; 195 mRingerMode = ringerMode; 196 updateRingOrNotificationPreference(); 197 } 198 199 private void updateEffectsSuppressor() { 200 final ComponentName suppressor = NotificationManager.from(mContext).getEffectsSuppressor(); 201 if (Objects.equals(suppressor, mSuppressor)) return; 202 mSuppressor = suppressor; 203 if (mRingOrNotificationPreference != null) { 204 final String text = suppressor != null ? 205 mContext.getString(com.android.internal.R.string.muted_by, 206 getSuppressorCaption(suppressor)) : null; 207 mRingOrNotificationPreference.setSuppressionText(text); 208 } 209 updateRingOrNotificationPreference(); 210 } 211 212 private String getSuppressorCaption(ComponentName suppressor) { 213 final PackageManager pm = mContext.getPackageManager(); 214 try { 215 final ServiceInfo info = pm.getServiceInfo(suppressor, 0); 216 if (info != null) { 217 final CharSequence seq = info.loadLabel(pm); 218 if (seq != null) { 219 final String str = seq.toString().trim(); 220 if (str.length() > 0) { 221 return str; 222 } 223 } 224 } 225 } catch (Throwable e) { 226 Log.w(TAG, "Error loading suppressor caption", e); 227 } 228 return suppressor.getPackageName(); 229 } 230 231 private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback { 232 private SeekBarVolumizer mCurrent; 233 234 @Override 235 public void onSampleStarting(SeekBarVolumizer sbv) { 236 if (mCurrent != null && mCurrent != sbv) { 237 mCurrent.stopSample(); 238 } 239 mCurrent = sbv; 240 if (mCurrent != null) { 241 mHandler.removeMessages(H.STOP_SAMPLE); 242 mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF); 243 } 244 } 245 246 @Override 247 public void onStreamValueChanged(int stream, int progress) { 248 // noop 249 } 250 251 public void stopSample() { 252 if (mCurrent != null) { 253 mCurrent.stopSample(); 254 } 255 } 256 }; 257 258 259 // === Phone & notification ringtone === 260 261 private void initRingtones(PreferenceCategory root) { 262 mPhoneRingtonePreference = root.findPreference(KEY_PHONE_RINGTONE); 263 if (mPhoneRingtonePreference != null && !mVoiceCapable) { 264 root.removePreference(mPhoneRingtonePreference); 265 mPhoneRingtonePreference = null; 266 } 267 mNotificationRingtonePreference = root.findPreference(KEY_NOTIFICATION_RINGTONE); 268 } 269 270 private void lookupRingtoneNames() { 271 AsyncTask.execute(mLookupRingtoneNames); 272 } 273 274 private final Runnable mLookupRingtoneNames = new Runnable() { 275 @Override 276 public void run() { 277 if (mPhoneRingtonePreference != null) { 278 final CharSequence summary = updateRingtoneName( 279 mContext, RingtoneManager.TYPE_RINGTONE); 280 if (summary != null) { 281 mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE, summary).sendToTarget(); 282 } 283 } 284 if (mNotificationRingtonePreference != null) { 285 final CharSequence summary = updateRingtoneName( 286 mContext, RingtoneManager.TYPE_NOTIFICATION); 287 if (summary != null) { 288 mHandler.obtainMessage(H.UPDATE_NOTIFICATION_RINGTONE, summary).sendToTarget(); 289 } 290 } 291 } 292 }; 293 294 private static CharSequence updateRingtoneName(Context context, int type) { 295 if (context == null) { 296 Log.e(TAG, "Unable to update ringtone name, no context provided"); 297 return null; 298 } 299 Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); 300 CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown); 301 // Is it a silent ringtone? 302 if (ringtoneUri == null) { 303 summary = context.getString(com.android.internal.R.string.ringtone_silent); 304 } else { 305 Cursor cursor = null; 306 try { 307 if (MediaStore.AUTHORITY.equals(ringtoneUri.getAuthority())) { 308 // Fetch the ringtone title from the media provider 309 cursor = context.getContentResolver().query(ringtoneUri, 310 new String[] { MediaStore.Audio.Media.TITLE }, null, null, null); 311 } else if (ContentResolver.SCHEME_CONTENT.equals(ringtoneUri.getScheme())) { 312 cursor = context.getContentResolver().query(ringtoneUri, 313 new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null); 314 } 315 if (cursor != null) { 316 if (cursor.moveToFirst()) { 317 summary = cursor.getString(0); 318 } 319 } 320 } catch (SQLiteException sqle) { 321 // Unknown title for the ringtone 322 } catch (IllegalArgumentException iae) { 323 // Some other error retrieving the column from the provider 324 } finally { 325 if (cursor != null) { 326 cursor.close(); 327 } 328 } 329 } 330 return summary; 331 } 332 333 // === Vibrate when ringing === 334 335 private void initVibrateWhenRinging(PreferenceCategory root) { 336 mVibrateWhenRinging = (TwoStatePreference) root.findPreference(KEY_VIBRATE_WHEN_RINGING); 337 if (mVibrateWhenRinging == null) { 338 Log.i(TAG, "Preference not found: " + KEY_VIBRATE_WHEN_RINGING); 339 return; 340 } 341 if (!mVoiceCapable) { 342 root.removePreference(mVibrateWhenRinging); 343 mVibrateWhenRinging = null; 344 return; 345 } 346 mVibrateWhenRinging.setPersistent(false); 347 updateVibrateWhenRinging(); 348 mVibrateWhenRinging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 349 @Override 350 public boolean onPreferenceChange(Preference preference, Object newValue) { 351 final boolean val = (Boolean) newValue; 352 return Settings.System.putInt(getContentResolver(), 353 Settings.System.VIBRATE_WHEN_RINGING, 354 val ? 1 : 0); 355 } 356 }); 357 } 358 359 private void updateVibrateWhenRinging() { 360 if (mVibrateWhenRinging == null) return; 361 mVibrateWhenRinging.setChecked(Settings.System.getInt(getContentResolver(), 362 Settings.System.VIBRATE_WHEN_RINGING, 0) != 0); 363 } 364 365 // === Pulse notification light === 366 367 private void initPulse(PreferenceCategory parent) { 368 mNotificationPulse = (TwoStatePreference) parent.findPreference(KEY_NOTIFICATION_PULSE); 369 if (mNotificationPulse == null) { 370 Log.i(TAG, "Preference not found: " + KEY_NOTIFICATION_PULSE); 371 return; 372 } 373 if (!getResources() 374 .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) { 375 parent.removePreference(mNotificationPulse); 376 } else { 377 updatePulse(); 378 mNotificationPulse.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 379 @Override 380 public boolean onPreferenceChange(Preference preference, Object newValue) { 381 final boolean val = (Boolean)newValue; 382 return Settings.System.putInt(getContentResolver(), 383 Settings.System.NOTIFICATION_LIGHT_PULSE, 384 val ? 1 : 0); 385 } 386 }); 387 } 388 } 389 390 private void updatePulse() { 391 if (mNotificationPulse == null) { 392 return; 393 } 394 try { 395 mNotificationPulse.setChecked(Settings.System.getInt(getContentResolver(), 396 Settings.System.NOTIFICATION_LIGHT_PULSE) == 1); 397 } catch (Settings.SettingNotFoundException snfe) { 398 Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found"); 399 } 400 } 401 402 // === Lockscreen (public / private) notifications === 403 404 private void initLockscreenNotifications(PreferenceCategory parent) { 405 mLockscreen = (DropDownPreference) parent.findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); 406 if (mLockscreen == null) { 407 Log.i(TAG, "Preference not found: " + KEY_LOCK_SCREEN_NOTIFICATIONS); 408 return; 409 } 410 411 mLockscreen.addItem(R.string.lock_screen_notifications_summary_show, 412 R.string.lock_screen_notifications_summary_show); 413 if (mSecure) { 414 mLockscreen.addItem(R.string.lock_screen_notifications_summary_hide, 415 R.string.lock_screen_notifications_summary_hide); 416 } 417 mLockscreen.addItem(R.string.lock_screen_notifications_summary_disable, 418 R.string.lock_screen_notifications_summary_disable); 419 updateLockscreenNotifications(); 420 mLockscreen.setCallback(new DropDownPreference.Callback() { 421 @Override 422 public boolean onItemSelected(int pos, Object value) { 423 final int val = (Integer) value; 424 if (val == mLockscreenSelectedValue) { 425 return true; 426 } 427 final boolean enabled = val != R.string.lock_screen_notifications_summary_disable; 428 final boolean show = val == R.string.lock_screen_notifications_summary_show; 429 Settings.Secure.putInt(getContentResolver(), 430 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, show ? 1 : 0); 431 Settings.Secure.putInt(getContentResolver(), 432 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, enabled ? 1 : 0); 433 mLockscreenSelectedValue = val; 434 return true; 435 } 436 }); 437 } 438 439 private void updateLockscreenNotifications() { 440 if (mLockscreen == null) { 441 return; 442 } 443 final boolean enabled = getLockscreenNotificationsEnabled(); 444 final boolean allowPrivate = !mSecure || getLockscreenAllowPrivateNotifications(); 445 mLockscreenSelectedValue = !enabled ? R.string.lock_screen_notifications_summary_disable : 446 allowPrivate ? R.string.lock_screen_notifications_summary_show : 447 R.string.lock_screen_notifications_summary_hide; 448 mLockscreen.setSelectedValue(mLockscreenSelectedValue); 449 } 450 451 private boolean getLockscreenNotificationsEnabled() { 452 return Settings.Secure.getInt(getContentResolver(), 453 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; 454 } 455 456 private boolean getLockscreenAllowPrivateNotifications() { 457 return Settings.Secure.getInt(getContentResolver(), 458 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; 459 } 460 461 // === Notification listeners === 462 463 private void refreshNotificationListeners() { 464 if (mNotificationAccess != null) { 465 final int total = NotificationAccessSettings.getListenersCount(mPM); 466 if (total == 0) { 467 getPreferenceScreen().removePreference(mNotificationAccess); 468 } else { 469 final int n = NotificationAccessSettings.getEnabledListenersCount(mContext); 470 if (n == 0) { 471 mNotificationAccess.setSummary(getResources().getString( 472 R.string.manage_notification_access_summary_zero)); 473 } else { 474 mNotificationAccess.setSummary(String.format(getResources().getQuantityString( 475 R.plurals.manage_notification_access_summary_nonzero, 476 n, n))); 477 } 478 } 479 } 480 } 481 482 // === Callbacks === 483 484 private final class SettingsObserver extends ContentObserver { 485 private final Uri VIBRATE_WHEN_RINGING_URI = 486 Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING); 487 private final Uri NOTIFICATION_LIGHT_PULSE_URI = 488 Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); 489 private final Uri LOCK_SCREEN_PRIVATE_URI = 490 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); 491 private final Uri LOCK_SCREEN_SHOW_URI = 492 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS); 493 494 public SettingsObserver() { 495 super(mHandler); 496 } 497 498 public void register(boolean register) { 499 final ContentResolver cr = getContentResolver(); 500 if (register) { 501 cr.registerContentObserver(VIBRATE_WHEN_RINGING_URI, false, this); 502 cr.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this); 503 cr.registerContentObserver(LOCK_SCREEN_PRIVATE_URI, false, this); 504 cr.registerContentObserver(LOCK_SCREEN_SHOW_URI, false, this); 505 } else { 506 cr.unregisterContentObserver(this); 507 } 508 } 509 510 @Override 511 public void onChange(boolean selfChange, Uri uri) { 512 super.onChange(selfChange, uri); 513 if (VIBRATE_WHEN_RINGING_URI.equals(uri)) { 514 updateVibrateWhenRinging(); 515 } 516 if (NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { 517 updatePulse(); 518 } 519 if (LOCK_SCREEN_PRIVATE_URI.equals(uri) || LOCK_SCREEN_SHOW_URI.equals(uri)) { 520 updateLockscreenNotifications(); 521 } 522 } 523 } 524 525 private final class H extends Handler { 526 private static final int UPDATE_PHONE_RINGTONE = 1; 527 private static final int UPDATE_NOTIFICATION_RINGTONE = 2; 528 private static final int STOP_SAMPLE = 3; 529 private static final int UPDATE_EFFECTS_SUPPRESSOR = 4; 530 private static final int UPDATE_RINGER_MODE = 5; 531 532 private H() { 533 super(Looper.getMainLooper()); 534 } 535 536 @Override 537 public void handleMessage(Message msg) { 538 switch (msg.what) { 539 case UPDATE_PHONE_RINGTONE: 540 mPhoneRingtonePreference.setSummary((CharSequence) msg.obj); 541 break; 542 case UPDATE_NOTIFICATION_RINGTONE: 543 mNotificationRingtonePreference.setSummary((CharSequence) msg.obj); 544 break; 545 case STOP_SAMPLE: 546 mVolumeCallback.stopSample(); 547 break; 548 case UPDATE_EFFECTS_SUPPRESSOR: 549 updateEffectsSuppressor(); 550 break; 551 case UPDATE_RINGER_MODE: 552 updateRingerMode(); 553 break; 554 } 555 } 556 } 557 558 private class Receiver extends BroadcastReceiver { 559 private boolean mRegistered; 560 561 public void register(boolean register) { 562 if (mRegistered == register) return; 563 if (register) { 564 final IntentFilter filter = new IntentFilter(); 565 filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); 566 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 567 mContext.registerReceiver(this, filter); 568 } else { 569 mContext.unregisterReceiver(this); 570 } 571 mRegistered = register; 572 } 573 574 @Override 575 public void onReceive(Context context, Intent intent) { 576 final String action = intent.getAction(); 577 if (NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED.equals(action)) { 578 mHandler.sendEmptyMessage(H.UPDATE_EFFECTS_SUPPRESSOR); 579 } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { 580 mHandler.sendEmptyMessage(H.UPDATE_RINGER_MODE); 581 } 582 } 583 } 584 585 // === Indexing === 586 587 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 588 new BaseSearchIndexProvider() { 589 590 public List<SearchIndexableResource> getXmlResourcesToIndex( 591 Context context, boolean enabled) { 592 final SearchIndexableResource sir = new SearchIndexableResource(context); 593 sir.xmlResId = R.xml.notification_settings; 594 return Arrays.asList(sir); 595 } 596 597 public List<String> getNonIndexableKeys(Context context) { 598 final ArrayList<String> rt = new ArrayList<String>(); 599 if (Utils.isVoiceCapable(context)) { 600 rt.add(KEY_NOTIFICATION_VOLUME); 601 } else { 602 rt.add(KEY_RING_VOLUME); 603 rt.add(KEY_PHONE_RINGTONE); 604 rt.add(KEY_VIBRATE_WHEN_RINGING); 605 } 606 return rt; 607 } 608 }; 609 } 610