RingtoneManager.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/* 2 * Copyright (C) 2007 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 android.media; 18 19import com.android.internal.database.SortCursor; 20 21import android.annotation.SdkConstant; 22import android.annotation.SdkConstant.SdkConstantType; 23import android.app.Activity; 24import android.content.ContentResolver; 25import android.content.ContentUris; 26import android.content.Context; 27import android.content.Intent; 28import android.content.res.AssetFileDescriptor; 29import android.database.Cursor; 30import android.database.MergeCursor; 31import android.net.Uri; 32import android.os.Environment; 33import android.os.ParcelFileDescriptor; 34import android.provider.DrmStore; 35import android.provider.MediaStore; 36import android.provider.Settings; 37import android.provider.Settings.System; 38import android.util.Config; 39import android.util.Log; 40 41import java.util.ArrayList; 42import java.util.List; 43 44/** 45 * RingtoneManager provides access to ringtones, notification, and other types 46 * of sounds. It manages querying the different media providers and combines the 47 * results into a single cursor. It also provides a {@link Ringtone} for each 48 * ringtone. We generically call these sounds ringtones, however the 49 * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the 50 * phone ringer. 51 * <p> 52 * To show a ringtone picker to the user, use the 53 * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity. 54 * 55 * @see Ringtone 56 */ 57public class RingtoneManager { 58 59 private static final String TAG = "RingtoneManager"; 60 61 // Make sure these are in sync with attrs.xml: 62 // <attr name="ringtoneType"> 63 64 /** 65 * Type that refers to sounds that are used for the phone ringer. 66 */ 67 public static final int TYPE_RINGTONE = 1; 68 69 /** 70 * Type that refers to sounds that are used for notifications. 71 */ 72 public static final int TYPE_NOTIFICATION = 2; 73 74 /** 75 * Type that refers to sounds that are used for the alarm. 76 */ 77 public static final int TYPE_ALARM = 4; 78 79 /** 80 * All types of sounds. 81 */ 82 public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM; 83 84 // </attr> 85 86 /** 87 * Activity Action: Shows a ringtone picker. 88 * <p> 89 * Input: {@link #EXTRA_RINGTONE_EXISTING_URI}, 90 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}, 91 * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE}, 92 * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE}, 93 * {@link #EXTRA_RINGTONE_INCLUDE_DRM}. 94 * <p> 95 * Output: {@link #EXTRA_RINGTONE_PICKED_URI}. 96 */ 97 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 98 public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER"; 99 100 /** 101 * Given to the ringtone picker as a boolean. Whether to show an item for 102 * "Default". 103 * 104 * @see #ACTION_RINGTONE_PICKER 105 */ 106 public static final String EXTRA_RINGTONE_SHOW_DEFAULT = 107 "android.intent.extra.ringtone.SHOW_DEFAULT"; 108 109 /** 110 * Given to the ringtone picker as a boolean. Whether to show an item for 111 * "Silent". If the "Silent" item is picked, 112 * {@link #EXTRA_RINGTONE_PICKED_URI} will be null. 113 * 114 * @see #ACTION_RINGTONE_PICKER 115 */ 116 public static final String EXTRA_RINGTONE_SHOW_SILENT = 117 "android.intent.extra.ringtone.SHOW_SILENT"; 118 119 /** 120 * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. 121 */ 122 public static final String EXTRA_RINGTONE_INCLUDE_DRM = 123 "android.intent.extra.ringtone.INCLUDE_DRM"; 124 125 /** 126 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 127 * current ringtone, which will be used to show a checkmark next to the item 128 * for this {@link Uri}. If showing an item for "Default" (@see 129 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of 130 * {@link System#DEFAULT_RINGTONE_URI} or 131 * {@link System#DEFAULT_NOTIFICATION_URI} to have the "Default" item 132 * checked. 133 * 134 * @see #ACTION_RINGTONE_PICKER 135 */ 136 public static final String EXTRA_RINGTONE_EXISTING_URI = 137 "android.intent.extra.ringtone.EXISTING_URI"; 138 139 /** 140 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 141 * ringtone to play when the user attempts to preview the "Default" 142 * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI} or 143 * {@link System#DEFAULT_NOTIFICATION_URI} to have the "Default" point to 144 * the current sound for the given default sound type. If you are showing a 145 * ringtone picker for some other type of sound, you are free to provide any 146 * {@link Uri} here. 147 */ 148 public static final String EXTRA_RINGTONE_DEFAULT_URI = 149 "android.intent.extra.ringtone.DEFAULT_URI"; 150 151 /** 152 * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be 153 * shown in the picker. One or more of {@link #TYPE_RINGTONE}, 154 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL} 155 * (bitwise-ored together). 156 */ 157 public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE"; 158 159 /** 160 * Given to the ringtone picker as a {@link CharSequence}. The title to 161 * show for the ringtone picker. This has a default value that is suitable 162 * in most cases. 163 */ 164 public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE"; 165 166 /** 167 * Returned from the ringtone picker as a {@link Uri}. 168 * <p> 169 * It will be one of: 170 * <li> the picked ringtone, 171 * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI} or 172 * {@link System#DEFAULT_NOTIFICATION_URI} if the default was chosen, 173 * <li> null if the "Silent" item was picked. 174 * 175 * @see #ACTION_RINGTONE_PICKER 176 */ 177 public static final String EXTRA_RINGTONE_PICKED_URI = 178 "android.intent.extra.ringtone.PICKED_URI"; 179 180 // Make sure the column ordering and then ..._COLUMN_INDEX are in sync 181 182 private static final String[] INTERNAL_COLUMNS = new String[] { 183 MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, 184 "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"" 185 }; 186 187 private static final String[] DRM_COLUMNS = new String[] { 188 DrmStore.Audio._ID, DrmStore.Audio.TITLE, 189 "\"" + DrmStore.Audio.CONTENT_URI + "\"" 190 }; 191 192 private static final String[] MEDIA_COLUMNS = new String[] { 193 MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, 194 "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"" 195 }; 196 197 /** 198 * The column index (in the cursor returned by {@link #getCursor()} for the 199 * row ID. 200 */ 201 public static final int ID_COLUMN_INDEX = 0; 202 203 /** 204 * The column index (in the cursor returned by {@link #getCursor()} for the 205 * title. 206 */ 207 public static final int TITLE_COLUMN_INDEX = 1; 208 209 /** 210 * The column index (in the cursor returned by {@link #getCursor()} for the 211 * media provider's URI. 212 */ 213 public static final int URI_COLUMN_INDEX = 2; 214 215 private Activity mActivity; 216 private Context mContext; 217 218 private Cursor mCursor; 219 220 private int mType = TYPE_RINGTONE; 221 222 /** 223 * If a column (item from this list) exists in the Cursor, its value must 224 * be true (value of 1) for the row to be returned. 225 */ 226 private List<String> mFilterColumns = new ArrayList<String>(); 227 228 private boolean mStopPreviousRingtone = true; 229 private Ringtone mPreviousRingtone; 230 231 private boolean mIncludeDrm; 232 233 /** 234 * Constructs a RingtoneManager. This constructor is recommended as its 235 * constructed instance manages cursor(s). 236 * 237 * @param activity The activity used to get a managed cursor. 238 */ 239 public RingtoneManager(Activity activity) { 240 mContext = mActivity = activity; 241 setType(mType); 242 } 243 244 /** 245 * Constructs a RingtoneManager. The instance constructed by this 246 * constructor will not manage the cursor(s), so the client should handle 247 * this itself. 248 * 249 * @param context The context to used to get a cursor. 250 */ 251 public RingtoneManager(Context context) { 252 mContext = context; 253 setType(mType); 254 } 255 256 /** 257 * Sets which type(s) of ringtones will be listed by this. 258 * 259 * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, 260 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, 261 * {@link #TYPE_ALL}. 262 * @see #EXTRA_RINGTONE_TYPE 263 */ 264 public void setType(int type) { 265 266 if (mCursor != null) { 267 throw new IllegalStateException( 268 "Setting filter columns should be done before querying for ringtones."); 269 } 270 271 mType = type; 272 setFilterColumnsList(type); 273 } 274 275 /** 276 * Whether retrieving another {@link Ringtone} will stop playing the 277 * previously retrieved {@link Ringtone}. 278 * <p> 279 * If this is false, make sure to {@link Ringtone#stop()} any previous 280 * ringtones to free resources. 281 * 282 * @param stopPreviousRingtone If true, the previously retrieved 283 * {@link Ringtone} will be stopped. 284 */ 285 public void setStopPreviousRingtone(boolean stopPreviousRingtone) { 286 mStopPreviousRingtone = stopPreviousRingtone; 287 } 288 289 /** 290 * @see #setStopPreviousRingtone(boolean) 291 */ 292 public boolean getStopPreviousRingtone() { 293 return mStopPreviousRingtone; 294 } 295 296 /** 297 * Stops playing the last {@link Ringtone} retrieved from this. 298 */ 299 public void stopPreviousRingtone() { 300 if (mPreviousRingtone != null) { 301 mPreviousRingtone.stop(); 302 } 303 } 304 305 /** 306 * Returns whether DRM ringtones will be included. 307 * 308 * @return Whether DRM ringtones will be included. 309 * @see #setIncludeDrm(boolean) 310 */ 311 public boolean getIncludeDrm() { 312 return mIncludeDrm; 313 } 314 315 /** 316 * Sets whether to include DRM ringtones. 317 * 318 * @param includeDrm Whether to include DRM ringtones. 319 */ 320 public void setIncludeDrm(boolean includeDrm) { 321 mIncludeDrm = includeDrm; 322 } 323 324 /** 325 * Returns a {@link Cursor} of all the ringtones available. The returned 326 * cursor will be the same cursor returned each time this method is called, 327 * so do not {@link Cursor#close()} the cursor. The cursor can be 328 * {@link Cursor#deactivate()} safely. 329 * <p> 330 * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the 331 * caller should manage the returned cursor through its activity's life 332 * cycle to prevent leaking the cursor. 333 * 334 * @return A {@link Cursor} of all the ringtones available. 335 * @see #ID_COLUMN_INDEX 336 * @see #TITLE_COLUMN_INDEX 337 * @see #URI_COLUMN_INDEX 338 */ 339 public Cursor getCursor() { 340 if (mCursor != null && mCursor.requery()) { 341 return mCursor; 342 } 343 344 final Cursor internalCursor = getInternalRingtones(); 345 final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null; 346 final Cursor mediaCursor = getMediaRingtones(); 347 348 return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor }, 349 MediaStore.MediaColumns.TITLE); 350 } 351 352 /** 353 * Gets a {@link Ringtone} for the ringtone at the given position in the 354 * {@link Cursor}. 355 * 356 * @param position The position (in the {@link Cursor}) of the ringtone. 357 * @return A {@link Ringtone} pointing to the ringtone. 358 */ 359 public Ringtone getRingtone(int position) { 360 if (mStopPreviousRingtone && mPreviousRingtone != null) { 361 mPreviousRingtone.stop(); 362 } 363 364 return mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position)); 365 } 366 367 /** 368 * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}. 369 * 370 * @param position The position (in the {@link Cursor}) of the ringtone. 371 * @return A {@link Uri} pointing to the ringtone. 372 */ 373 public Uri getRingtoneUri(int position) { 374 final Cursor cursor = getCursor(); 375 376 if (!cursor.moveToPosition(position)) { 377 return null; 378 } 379 380 return getUriFromCursor(cursor); 381 } 382 383 private static Uri getUriFromCursor(Cursor cursor) { 384 return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor 385 .getLong(ID_COLUMN_INDEX)); 386 } 387 388 /** 389 * Gets the position of a {@link Uri} within this {@link RingtoneManager}. 390 * 391 * @param ringtoneUri The {@link Uri} to retreive the position of. 392 * @return The position of the {@link Uri}, or -1 if it cannot be found. 393 */ 394 public int getRingtonePosition(Uri ringtoneUri) { 395 396 if (ringtoneUri == null) return -1; 397 398 final Cursor cursor = getCursor(); 399 final int cursorCount = cursor.getCount(); 400 401 if (!cursor.moveToFirst()) { 402 return -1; 403 } 404 405 // Only create Uri objects when the actual URI changes 406 Uri currentUri = null; 407 String previousUriString = null; 408 for (int i = 0; i < cursorCount; i++) { 409 String uriString = cursor.getString(URI_COLUMN_INDEX); 410 if (currentUri == null || !uriString.equals(previousUriString)) { 411 currentUri = Uri.parse(uriString); 412 } 413 414 if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor 415 .getLong(ID_COLUMN_INDEX)))) { 416 return i; 417 } 418 419 cursor.move(1); 420 421 previousUriString = uriString; 422 } 423 424 return -1; 425 } 426 427 /** 428 * Returns a valid ringtone URI. No guarantees on which it returns. If it 429 * cannot find one, returns null. 430 * 431 * @param context The context to use for querying. 432 * @return A ringtone URI, or null if one cannot be found. 433 */ 434 public static Uri getValidRingtoneUri(Context context) { 435 final RingtoneManager rm = new RingtoneManager(context); 436 437 Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); 438 439 if (uri == null) { 440 uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); 441 } 442 443 if (uri == null) { 444 uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones()); 445 } 446 447 return uri; 448 } 449 450 private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) { 451 if (cursor != null) { 452 Uri uri = null; 453 454 if (cursor.moveToFirst()) { 455 uri = getUriFromCursor(cursor); 456 } 457 cursor.close(); 458 459 return uri; 460 } else { 461 return null; 462 } 463 } 464 465 private Cursor getInternalRingtones() { 466 return query( 467 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, 468 constructBooleanTrueWhereClause(mFilterColumns), 469 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 470 } 471 472 private Cursor getDrmRingtones() { 473 // DRM store does not have any columns to use for filtering 474 return query( 475 DrmStore.Audio.CONTENT_URI, DRM_COLUMNS, 476 null, null, DrmStore.Audio.TITLE); 477 } 478 479 private Cursor getMediaRingtones() { 480 // Get the external media cursor. First check to see if it is mounted. 481 final String status = Environment.getExternalStorageState(); 482 483 return (status.equals(Environment.MEDIA_MOUNTED) || 484 status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) 485 ? query( 486 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, 487 constructBooleanTrueWhereClause(mFilterColumns), null, 488 MediaStore.Audio.Media.DEFAULT_SORT_ORDER) 489 : null; 490 } 491 492 private void setFilterColumnsList(int type) { 493 List<String> columns = mFilterColumns; 494 columns.clear(); 495 496 if ((type & TYPE_RINGTONE) != 0) { 497 columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE); 498 } 499 500 if ((type & TYPE_NOTIFICATION) != 0) { 501 columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION); 502 } 503 504 if ((type & TYPE_ALARM) != 0) { 505 columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); 506 } 507 } 508 509 /** 510 * Constructs a where clause that consists of at least one column being 1 511 * (true). This is used to find all matching sounds for the given sound 512 * types (ringtone, notifications, etc.) 513 * 514 * @param columns The columns that must be true. 515 * @return The where clause. 516 */ 517 private static String constructBooleanTrueWhereClause(List<String> columns) { 518 519 if (columns == null) return null; 520 521 StringBuilder sb = new StringBuilder(); 522 for (int i = columns.size() - 1; i >= 0; i--) { 523 sb.append(columns.get(i)).append("=1 or "); 524 } 525 526 if (columns.size() > 0) { 527 // Remove last ' or ' 528 sb.setLength(sb.length() - 4); 529 } 530 531 return sb.toString(); 532 } 533 534 private Cursor query(Uri uri, 535 String[] projection, 536 String selection, 537 String[] selectionArgs, 538 String sortOrder) { 539 if (mActivity != null) { 540 return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder); 541 } else { 542 return mContext.getContentResolver().query(uri, projection, selection, selectionArgs, 543 sortOrder); 544 } 545 } 546 547 /** 548 * Returns a {@link Ringtone} for a given sound URI. 549 * <p> 550 * If the given URI cannot be opened for any reason, this method will 551 * attempt to fallback on another sound. If it cannot find any, it will 552 * return null. 553 * 554 * @param context A context used to query. 555 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 556 * @return A {@link Ringtone} for the given URI, or null. 557 */ 558 public static Ringtone getRingtone(final Context context, Uri ringtoneUri) { 559 ContentResolver res = context.getContentResolver(); 560 561 try { 562 Ringtone r = new Ringtone(context); 563 r.open(ringtoneUri); 564 return r; 565 } catch (Exception ex) { 566 Log.e(TAG, "Failed to open ringtone " + ringtoneUri); 567 } 568 569 // Ringtone doesn't exist, use the fallback ringtone. 570 try { 571 AssetFileDescriptor afd = context.getResources().openRawResourceFd( 572 com.android.internal.R.raw.fallbackring); 573 if (afd != null) { 574 Ringtone r = new Ringtone(context); 575 r.open(afd); 576 afd.close(); 577 return r; 578 } 579 } catch (Exception ex) { 580 } 581 582 // we should never get here 583 Log.e(TAG, "unable to find a usable ringtone"); 584 return null; 585 } 586 587 588 /** 589 * Gets the current default sound's {@link Uri}. This will give the actual 590 * sound {@link Uri}, instead of using this, most clients can use 591 * {@link System#DEFAULT_RINGTONE_URI}. 592 * 593 * @param context A context used for querying. 594 * @param type The type whose default sound should be returned. One of 595 * {@link #TYPE_RINGTONE} or {@link #TYPE_NOTIFICATION}. 596 * @return A {@link Uri} pointing to the default sound for the sound type. 597 * @see #setActualDefaultRingtoneUri(Context, int, Uri) 598 */ 599 public static Uri getActualDefaultRingtoneUri(Context context, int type) { 600 String setting = getSettingForType(type); 601 if (setting == null) return null; 602 final String uriString = Settings.System.getString(context.getContentResolver(), setting); 603 return uriString != null ? Uri.parse(uriString) : getValidRingtoneUri(context); 604 } 605 606 /** 607 * Sets the {@link Uri} of the default sound for a given sound type. 608 * 609 * @param context A context used for querying. 610 * @param type The type whose default sound should be set. One of 611 * {@link #TYPE_RINGTONE} or {@link #TYPE_NOTIFICATION}. 612 * @param ringtoneUri A {@link Uri} pointing to the default sound to set. 613 * @see #getActualDefaultRingtoneUri(Context, int) 614 */ 615 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { 616 String setting = getSettingForType(type); 617 if (setting == null) return; 618 Settings.System.putString(context.getContentResolver(), setting, ringtoneUri.toString()); 619 } 620 621 private static String getSettingForType(int type) { 622 if ((type & TYPE_RINGTONE) != 0) { 623 return Settings.System.RINGTONE; 624 } else if ((type & TYPE_NOTIFICATION) != 0) { 625 return Settings.System.NOTIFICATION_SOUND; 626 } else { 627 return null; 628 } 629 } 630 631 /** 632 * Returns whether the given {@link Uri} is one of the default ringtones. 633 * 634 * @param ringtoneUri The ringtone {@link Uri} to be checked. 635 * @return Whether the {@link Uri} is a default. 636 */ 637 public static boolean isDefault(Uri ringtoneUri) { 638 return getDefaultType(ringtoneUri) != -1; 639 } 640 641 /** 642 * Returns the type of a default {@link Uri}. 643 * 644 * @param defaultRingtoneUri The default {@link Uri}. For example, 645 * {@link System#DEFAULT_RINGTONE_URI} or 646 * {@link System#DEFAULT_NOTIFICATION_URI}. 647 * @return The type of the defaultRingtoneUri, or -1. 648 */ 649 public static int getDefaultType(Uri defaultRingtoneUri) { 650 if (defaultRingtoneUri == null) { 651 return -1; 652 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) { 653 return TYPE_RINGTONE; 654 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { 655 return TYPE_NOTIFICATION; 656 } else { 657 return -1; 658 } 659 } 660 661 /** 662 * Returns the {@link Uri} for the default ringtone of a particular type. 663 * Rather than returning the actual ringtone's sound {@link Uri}, this will 664 * return the symbolic {@link Uri} which will resolved to the actual sound 665 * when played. 666 * 667 * @param type The ringtone type whose default should be returned. 668 * @return The {@link Uri} of the default ringtone for the given type. 669 */ 670 public static Uri getDefaultUri(int type) { 671 if ((type & TYPE_RINGTONE) != 0) { 672 return Settings.System.DEFAULT_RINGTONE_URI; 673 } else if ((type & TYPE_NOTIFICATION) != 0) { 674 return Settings.System.DEFAULT_NOTIFICATION_URI; 675 } else { 676 return null; 677 } 678 } 679 680} 681