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 17 package com.android.providers.media; 18 19 import android.content.ContentProvider; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.res.Resources; 24 import android.content.res.Resources.NotFoundException; 25 import android.database.Cursor; 26 import android.database.CursorWrapper; 27 import android.media.AudioAttributes; 28 import android.media.Ringtone; 29 import android.media.RingtoneManager; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.Environment; 34 import android.os.Handler; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.provider.MediaStore; 38 import android.provider.Settings; 39 import android.util.Log; 40 import android.util.TypedValue; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.AdapterView; 45 import android.widget.CursorAdapter; 46 import android.widget.ImageView; 47 import android.widget.ListView; 48 import android.widget.TextView; 49 import android.widget.Toast; 50 51 import com.android.internal.app.AlertActivity; 52 import com.android.internal.app.AlertController; 53 54 import java.io.IOException; 55 import java.util.Objects; 56 import java.util.regex.Pattern; 57 58 /** 59 * The {@link RingtonePickerActivity} allows the user to choose one from all of the 60 * available ringtones. The chosen ringtone's URI will be persisted as a string. 61 * 62 * @see RingtoneManager#ACTION_RINGTONE_PICKER 63 */ 64 public final class RingtonePickerActivity extends AlertActivity implements 65 AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener, 66 AlertController.AlertParams.OnPrepareListViewListener { 67 68 private static final int POS_UNKNOWN = -1; 69 70 private static final String TAG = "RingtonePickerActivity"; 71 72 private static final int DELAY_MS_SELECTION_PLAYED = 300; 73 74 private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE; 75 76 private static final String SAVE_CLICKED_POS = "clicked_pos"; 77 78 private static final String SOUND_NAME_RES_PREFIX = "sound_name_"; 79 80 private static final int ADD_FILE_REQUEST_CODE = 300; 81 82 private RingtoneManager mRingtoneManager; 83 private int mType; 84 85 private Cursor mCursor; 86 private Handler mHandler; 87 private BadgedRingtoneAdapter mAdapter; 88 89 /** The position in the list of the 'Silent' item. */ 90 private int mSilentPos = POS_UNKNOWN; 91 92 /** The position in the list of the 'Default' item. */ 93 private int mDefaultRingtonePos = POS_UNKNOWN; 94 95 /** The position in the list of the ringtone to sample. */ 96 private int mSampleRingtonePos = POS_UNKNOWN; 97 98 /** Whether this list has the 'Silent' item. */ 99 private boolean mHasSilentItem; 100 101 /** The Uri to place a checkmark next to. */ 102 private Uri mExistingUri; 103 104 /** The number of static items in the list. */ 105 private int mStaticItemCount; 106 107 /** Whether this list has the 'Default' item. */ 108 private boolean mHasDefaultItem; 109 110 /** The Uri to play when the 'Default' item is clicked. */ 111 private Uri mUriForDefaultItem; 112 113 /** Id of the user to which the ringtone picker should list the ringtones */ 114 private int mPickerUserId; 115 116 /** Context of the user specified by mPickerUserId */ 117 private Context mTargetContext; 118 119 /** 120 * A Ringtone for the default ringtone. In most cases, the RingtoneManager 121 * will stop the previous ringtone. However, the RingtoneManager doesn't 122 * manage the default ringtone for us, so we should stop this one manually. 123 */ 124 private Ringtone mDefaultRingtone; 125 126 /** 127 * The ringtone that's currently playing, unless the currently playing one is the default 128 * ringtone. 129 */ 130 private Ringtone mCurrentRingtone; 131 132 /** 133 * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked). 134 */ 135 private long mCheckedItemId = -1; 136 137 private int mAttributesFlags; 138 139 private boolean mShowOkCancelButtons; 140 141 /** 142 * Keep the currently playing ringtone around when changing orientation, so that it 143 * can be stopped later, after the activity is recreated. 144 */ 145 private static Ringtone sPlayingRingtone; 146 147 private DialogInterface.OnClickListener mRingtoneClickListener = 148 new DialogInterface.OnClickListener() { 149 150 /* 151 * On item clicked 152 */ 153 public void onClick(DialogInterface dialog, int which) { 154 if (which == mCursor.getCount() + mStaticItemCount) { 155 // The "Add new ringtone" item was clicked. Start a file picker intent to select 156 // only audio files (MIME type "audio/*") 157 final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); 158 chooseFile.setType("audio/*"); 159 chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, 160 new String[] { "audio/*", "application/ogg" }); 161 startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE); 162 return; 163 } 164 165 // Save the position of most recently clicked item 166 setCheckedItem(which); 167 168 // In the buttonless (watch-only) version, preemptively set our result since we won't 169 // have another chance to do so before the activity closes. 170 if (!mShowOkCancelButtons) { 171 setResultFromSelection(); 172 } 173 174 // Play clip 175 playRingtone(which, 0); 176 } 177 178 }; 179 180 @Override 181 protected void onCreate(Bundle savedInstanceState) { 182 super.onCreate(savedInstanceState); 183 184 mHandler = new Handler(); 185 186 Intent intent = getIntent(); 187 mPickerUserId = UserHandle.myUserId(); 188 mTargetContext = this; 189 190 // Get the types of ringtones to show 191 mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1); 192 initRingtoneManager(); 193 194 /* 195 * Get whether to show the 'Default' item, and the URI to play when the 196 * default is clicked 197 */ 198 mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); 199 mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); 200 if (mUriForDefaultItem == null) { 201 if (mType == RingtoneManager.TYPE_NOTIFICATION) { 202 mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI; 203 } else if (mType == RingtoneManager.TYPE_ALARM) { 204 mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI; 205 } else if (mType == RingtoneManager.TYPE_RINGTONE) { 206 mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; 207 } else { 208 // or leave it null for silence. 209 mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; 210 } 211 } 212 213 // Get whether to show the 'Silent' item 214 mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); 215 // AudioAttributes flags 216 mAttributesFlags |= intent.getIntExtra( 217 RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, 218 0 /*defaultValue == no flags*/); 219 220 mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); 221 222 // The volume keys will control the stream that we are choosing a ringtone for 223 setVolumeControlStream(mRingtoneManager.inferStreamType()); 224 225 // Get the URI whose list item should have a checkmark 226 mExistingUri = intent 227 .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); 228 229 // Create the list of ringtones and hold on to it so we can update later. 230 mAdapter = new BadgedRingtoneAdapter(this, mCursor, 231 /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId)); 232 if (savedInstanceState != null) { 233 setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN)); 234 } 235 236 final AlertController.AlertParams p = mAlertParams; 237 p.mAdapter = mAdapter; 238 p.mOnClickListener = mRingtoneClickListener; 239 p.mLabelColumn = COLUMN_LABEL; 240 p.mIsSingleChoice = true; 241 p.mOnItemSelectedListener = this; 242 if (mShowOkCancelButtons) { 243 p.mPositiveButtonText = getString(com.android.internal.R.string.ok); 244 p.mPositiveButtonListener = this; 245 p.mNegativeButtonText = getString(com.android.internal.R.string.cancel); 246 p.mPositiveButtonListener = this; 247 } 248 p.mOnPrepareListViewListener = this; 249 250 p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); 251 if (p.mTitle == null) { 252 if (mType == RingtoneManager.TYPE_ALARM) { 253 p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm); 254 } else if (mType == RingtoneManager.TYPE_NOTIFICATION) { 255 p.mTitle = 256 getString(com.android.internal.R.string.ringtone_picker_title_notification); 257 } else { 258 p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title); 259 } 260 } 261 262 setupAlert(); 263 } 264 @Override 265 public void onSaveInstanceState(Bundle outState) { 266 super.onSaveInstanceState(outState); 267 outState.putInt(SAVE_CLICKED_POS, getCheckedItem()); 268 } 269 270 @Override 271 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 272 super.onActivityResult(requestCode, resultCode, data); 273 274 if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) { 275 // Add the custom ringtone in a separate thread 276 final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() { 277 @Override 278 protected Uri doInBackground(Uri... params) { 279 try { 280 return mRingtoneManager.addCustomExternalRingtone(params[0], mType); 281 } catch (IOException | IllegalArgumentException e) { 282 Log.e(TAG, "Unable to add new ringtone", e); 283 } 284 return null; 285 } 286 287 @Override 288 protected void onPostExecute(Uri ringtoneUri) { 289 if (ringtoneUri != null) { 290 requeryForAdapter(); 291 } else { 292 // Ringtone was not added, display error Toast 293 Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone, 294 Toast.LENGTH_SHORT).show(); 295 } 296 } 297 }; 298 installTask.execute(data.getData()); 299 } 300 } 301 302 // Disabled because context menus aren't Material Design :( 303 /* 304 @Override 305 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 306 int position = ((AdapterContextMenuInfo) menuInfo).position; 307 308 Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position)); 309 if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) { 310 // It's a custom ringtone so we display the context menu 311 menu.setHeaderTitle(ringtone.getTitle(this)); 312 menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text); 313 } 314 } 315 316 @Override 317 public boolean onContextItemSelected(MenuItem item) { 318 switch (item.getItemId()) { 319 case Menu.FIRST: { 320 int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position; 321 Uri deletedRingtoneUri = getRingtone( 322 getRingtoneManagerPosition(deletedRingtonePos)).getUri(); 323 if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) { 324 requeryForAdapter(); 325 } else { 326 Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT) 327 .show(); 328 } 329 return true; 330 } 331 default: { 332 return false; 333 } 334 } 335 } 336 */ 337 338 @Override 339 public void onDestroy() { 340 if (mCursor != null) { 341 mCursor.close(); 342 mCursor = null; 343 } 344 super.onDestroy(); 345 } 346 347 public void onPrepareListView(ListView listView) { 348 // Reset the static item count, as this method can be called multiple times 349 mStaticItemCount = 0; 350 351 if (mHasDefaultItem) { 352 mDefaultRingtonePos = addDefaultRingtoneItem(listView); 353 354 if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) { 355 setCheckedItem(mDefaultRingtonePos); 356 } 357 } 358 359 if (mHasSilentItem) { 360 mSilentPos = addSilentItem(listView); 361 362 // The 'Silent' item should use a null Uri 363 if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) { 364 setCheckedItem(mSilentPos); 365 } 366 } 367 368 if (getCheckedItem() == POS_UNKNOWN) { 369 setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri))); 370 } 371 372 // In the buttonless (watch-only) version, preemptively set our result since we won't 373 // have another chance to do so before the activity closes. 374 if (!mShowOkCancelButtons) { 375 setResultFromSelection(); 376 } 377 // If external storage is available, add a button to install sounds from storage. 378 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 379 addNewRingtoneItem(listView); 380 } 381 382 // Enable context menu in ringtone items 383 registerForContextMenu(listView); 384 } 385 386 /** 387 * Re-query RingtoneManager for the most recent set of installed ringtones. May move the 388 * selected item position to match the new position of the chosen sound. 389 * 390 * This should only need to happen after adding or removing a ringtone. 391 */ 392 private void requeryForAdapter() { 393 // Refresh and set a new cursor, closing the old one. 394 initRingtoneManager(); 395 mAdapter.changeCursor(mCursor); 396 397 // Update checked item location. 398 int checkedPosition = POS_UNKNOWN; 399 for (int i = 0; i < mAdapter.getCount(); i++) { 400 if (mAdapter.getItemId(i) == mCheckedItemId) { 401 checkedPosition = getListPosition(i); 402 break; 403 } 404 } 405 if (mHasSilentItem && checkedPosition == POS_UNKNOWN) { 406 checkedPosition = mSilentPos; 407 } 408 setCheckedItem(checkedPosition); 409 setupAlert(); 410 } 411 412 /** 413 * Adds a static item to the top of the list. A static item is one that is not from the 414 * RingtoneManager. 415 * 416 * @param listView The ListView to add to. 417 * @param textResId The resource ID of the text for the item. 418 * @return The position of the inserted item. 419 */ 420 private int addStaticItem(ListView listView, int textResId) { 421 TextView textView = (TextView) getLayoutInflater().inflate( 422 com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false); 423 textView.setText(textResId); 424 listView.addHeaderView(textView); 425 mStaticItemCount++; 426 return listView.getHeaderViewsCount() - 1; 427 } 428 429 private int addDefaultRingtoneItem(ListView listView) { 430 if (mType == RingtoneManager.TYPE_NOTIFICATION) { 431 return addStaticItem(listView, R.string.notification_sound_default); 432 } else if (mType == RingtoneManager.TYPE_ALARM) { 433 return addStaticItem(listView, R.string.alarm_sound_default); 434 } 435 436 return addStaticItem(listView, R.string.ringtone_default); 437 } 438 439 private int addSilentItem(ListView listView) { 440 return addStaticItem(listView, com.android.internal.R.string.ringtone_silent); 441 } 442 443 private void addNewRingtoneItem(ListView listView) { 444 listView.addFooterView(getLayoutInflater().inflate(R.layout.add_ringtone_item, listView, 445 false /* attachToRoot */)); 446 } 447 448 private void initRingtoneManager() { 449 // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it 450 // causes unexpected behavior. 451 mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true); 452 if (mType != -1) { 453 mRingtoneManager.setType(mType); 454 } 455 mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL); 456 } 457 458 private Ringtone getRingtone(int ringtoneManagerPosition) { 459 if (ringtoneManagerPosition < 0) { 460 return null; 461 } 462 return mRingtoneManager.getRingtone(ringtoneManagerPosition); 463 } 464 465 private int getCheckedItem() { 466 return mAlertParams.mCheckedItem; 467 } 468 469 private void setCheckedItem(int pos) { 470 mAlertParams.mCheckedItem = pos; 471 mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos)); 472 } 473 474 /* 475 * On click of Ok/Cancel buttons 476 */ 477 public void onClick(DialogInterface dialog, int which) { 478 boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; 479 480 // Stop playing the previous ringtone 481 mRingtoneManager.stopPreviousRingtone(); 482 483 if (positiveResult) { 484 setResultFromSelection(); 485 } else { 486 setResult(RESULT_CANCELED); 487 } 488 489 finish(); 490 } 491 492 /* 493 * On item selected via keys 494 */ 495 public void onItemSelected(AdapterView parent, View view, int position, long id) { 496 playRingtone(position, DELAY_MS_SELECTION_PLAYED); 497 498 // In the buttonless (watch-only) version, preemptively set our result since we won't 499 // have another chance to do so before the activity closes. 500 if (!mShowOkCancelButtons) { 501 setResultFromSelection(); 502 } 503 } 504 505 public void onNothingSelected(AdapterView parent) { 506 } 507 508 private void playRingtone(int position, int delayMs) { 509 mHandler.removeCallbacks(this); 510 mSampleRingtonePos = position; 511 mHandler.postDelayed(this, delayMs); 512 } 513 514 public void run() { 515 stopAnyPlayingRingtone(); 516 if (mSampleRingtonePos == mSilentPos) { 517 return; 518 } 519 520 Ringtone ringtone; 521 if (mSampleRingtonePos == mDefaultRingtonePos) { 522 if (mDefaultRingtone == null) { 523 mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem); 524 } 525 /* 526 * Stream type of mDefaultRingtone is not set explicitly here. 527 * It should be set in accordance with mRingtoneManager of this Activity. 528 */ 529 if (mDefaultRingtone != null) { 530 mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType()); 531 } 532 ringtone = mDefaultRingtone; 533 mCurrentRingtone = null; 534 } else { 535 ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos)); 536 mCurrentRingtone = ringtone; 537 } 538 539 if (ringtone != null) { 540 if (mAttributesFlags != 0) { 541 ringtone.setAudioAttributes( 542 new AudioAttributes.Builder(ringtone.getAudioAttributes()) 543 .setFlags(mAttributesFlags) 544 .build()); 545 } 546 ringtone.play(); 547 } 548 } 549 550 @Override 551 protected void onStop() { 552 super.onStop(); 553 554 if (!isChangingConfigurations()) { 555 stopAnyPlayingRingtone(); 556 } else { 557 saveAnyPlayingRingtone(); 558 } 559 } 560 561 @Override 562 protected void onPause() { 563 super.onPause(); 564 if (!isChangingConfigurations()) { 565 stopAnyPlayingRingtone(); 566 } 567 } 568 569 private void setResultFromSelection() { 570 // Obtain the currently selected ringtone 571 Uri uri = null; 572 if (getCheckedItem() == mDefaultRingtonePos) { 573 // Set it to the default Uri that they originally gave us 574 uri = mUriForDefaultItem; 575 } else if (getCheckedItem() == mSilentPos) { 576 // A null Uri is for the 'Silent' item 577 uri = null; 578 } else { 579 uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem())); 580 } 581 582 // Return new URI if another ringtone was selected, as there's no ok/cancel button 583 if (Objects.equals(uri, mExistingUri)) { 584 setResult(RESULT_CANCELED); 585 } else { 586 Intent resultIntent = new Intent(); 587 resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri); 588 setResult(RESULT_OK, resultIntent); 589 } 590 } 591 592 private void saveAnyPlayingRingtone() { 593 if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { 594 sPlayingRingtone = mDefaultRingtone; 595 } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) { 596 sPlayingRingtone = mCurrentRingtone; 597 } 598 } 599 600 private void stopAnyPlayingRingtone() { 601 if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) { 602 sPlayingRingtone.stop(); 603 } 604 sPlayingRingtone = null; 605 606 if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { 607 mDefaultRingtone.stop(); 608 } 609 610 if (mRingtoneManager != null) { 611 mRingtoneManager.stopPreviousRingtone(); 612 } 613 } 614 615 private int getRingtoneManagerPosition(int listPos) { 616 return listPos - mStaticItemCount; 617 } 618 619 private int getListPosition(int ringtoneManagerPos) { 620 621 // If the manager position is -1 (for not found), return that 622 if (ringtoneManagerPos < 0) return ringtoneManagerPos; 623 624 return ringtoneManagerPos + mStaticItemCount; 625 } 626 627 private static class LocalizedCursor extends CursorWrapper { 628 629 final int mTitleIndex; 630 final Resources mResources; 631 String mNamePrefix; 632 final Pattern mSanitizePattern; 633 634 LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) { 635 super(cursor); 636 mTitleIndex = mCursor.getColumnIndex(columnLabel); 637 mResources = resources; 638 mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]"); 639 if (mTitleIndex == -1) { 640 Log.e(TAG, "No index for column " + columnLabel); 641 mNamePrefix = null; 642 } else { 643 try { 644 // Build the prefix for the name of the resource to look up 645 // format is: "ResourcePackageName::ResourceTypeName/" 646 // (the type name is expected to be "string" but let's not hardcode it). 647 // Here we use an existing resource "notification_sound_default" which is 648 // always expected to be found. 649 mNamePrefix = String.format("%s:%s/%s", 650 mResources.getResourcePackageName(R.string.notification_sound_default), 651 mResources.getResourceTypeName(R.string.notification_sound_default), 652 SOUND_NAME_RES_PREFIX); 653 } catch (NotFoundException e) { 654 mNamePrefix = null; 655 } 656 } 657 } 658 659 /** 660 * Process resource name to generate a valid resource name. 661 * @param input 662 * @return a non-null String 663 */ 664 private String sanitize(String input) { 665 if (input == null) { 666 return ""; 667 } 668 return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(); 669 } 670 671 @Override 672 public String getString(int columnIndex) { 673 final String defaultName = mCursor.getString(columnIndex); 674 if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) { 675 return defaultName; 676 } 677 TypedValue value = new TypedValue(); 678 try { 679 // the name currently in the database is used to derive a name to match 680 // against resource names in this package 681 mResources.getValue(mNamePrefix + sanitize(defaultName), value, false); 682 } catch (NotFoundException e) { 683 // no localized string, use the default string 684 return defaultName; 685 } 686 if ((value != null) && (value.type == TypedValue.TYPE_STRING)) { 687 Log.d(TAG, String.format("Replacing name %s with %s", 688 defaultName, value.string.toString())); 689 return value.string.toString(); 690 } else { 691 Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName); 692 return defaultName; 693 } 694 } 695 } 696 697 private class BadgedRingtoneAdapter extends CursorAdapter { 698 private final boolean mIsManagedProfile; 699 700 public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) { 701 super(context, cursor); 702 mIsManagedProfile = isManagedProfile; 703 } 704 705 @Override 706 public long getItemId(int position) { 707 if (position < 0) { 708 return position; 709 } 710 return super.getItemId(position); 711 } 712 713 @Override 714 public View newView(Context context, Cursor cursor, ViewGroup parent) { 715 LayoutInflater inflater = LayoutInflater.from(context); 716 return inflater.inflate(R.layout.radio_with_work_badge, parent, false); 717 } 718 719 @Override 720 public void bindView(View view, Context context, Cursor cursor) { 721 // Set text as the title of the ringtone 722 ((TextView) view.findViewById(R.id.checked_text_view)) 723 .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)); 724 725 boolean isWorkRingtone = false; 726 if (mIsManagedProfile) { 727 /* 728 * Display the work icon if the ringtone belongs to a work profile. We can tell that 729 * a ringtone belongs to a work profile if the picker user is a managed profile, the 730 * ringtone Uri is in external storage, and either the uri has no user id or has the 731 * id of the picker user 732 */ 733 Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition()); 734 int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId); 735 Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri); 736 737 if (uriUserId == mPickerUserId && uriWithoutUserId.toString() 738 .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) { 739 isWorkRingtone = true; 740 } 741 } 742 743 ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon); 744 if(isWorkRingtone) { 745 workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground( 746 UserHandle.of(mPickerUserId), -1 /* density */)); 747 workIcon.setVisibility(View.VISIBLE); 748 } else { 749 workIcon.setVisibility(View.GONE); 750 } 751 } 752 } 753 } 754