Loading AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ <!-- READ_PHONE_STATE is required to determine when a phone call exists prior to M --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- READ_EXTERNAL_STORAGE is required to play custom ringtones from the SD card prior to M --> <!-- It is also required to display user-friendly names for custom ringtones in the UI. --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:label="@string/app_label" Loading res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,9 @@ <!-- Setting labels on Set alarm screen: Select alarm ringtone --> <string name="alert">Alarm Ringtone</string> <!-- Label on expanded alarm edit view indicating the ringtone is custom. --> <string name="custom_ringtone">Custom Ringtone</string> <!-- Label on expanded alarm edit view. --> <string name="ringtone">Ringtone</string> Loading src/com/android/deskclock/AlarmClockFragment.java +30 −6 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.deskclock; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; Loading Loading @@ -105,6 +106,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements private static final String KEY_SELECTED_ALARM = "selectedAlarm"; private static final int REQUEST_CODE_RINGTONE = 1; private static final int REQUEST_CODE_PERMISSIONS = 2; private static final long INVALID_ID = -1; private static final String PREF_KEY_DEFAULT_ALARM_RINGTONE_URI = "default_alarm_ringtone_uri"; Loading Loading @@ -473,6 +475,13 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements setDefaultRingtoneUri(uri); asyncUpdateAlarm(mSelectedAlarm, false); // If the user chose an external ringtone and has not yet granted the permission to read // external storage, ask them for that permission now. if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(getActivity(), uri)) { final String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE}; requestPermissions(perms, REQUEST_CODE_PERMISSIONS); } } private Uri getDefaultRingtoneUri() { Loading Loading @@ -512,6 +521,14 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { // The permission change may alter the cached ringtone titles so clear them. // (e.g. READ_EXTERNAL_STORAGE is granted or revoked) mRingtoneTitleCache.clear(); } private class AlarmItemAdapter extends CursorAdapter { private final Context mContext; private final LayoutInflater mFactory; Loading Loading @@ -1083,13 +1100,20 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements // Try the cache first String title = mRingtoneTitleCache.getString(uri.toString()); if (title == null) { // If the user cannot read the ringtone file, insert our own name rather than the // ugly one returned by Ringtone.getTitle(). if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(mContext, uri)) { title = getString(R.string.custom_ringtone); } else { // This is slow because a media player is created during Ringtone object creation. Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); final Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); if (ringTone == null) { LogUtils.i("No ringtone for uri %s", uri.toString()); return null; } title = ringTone.getTitle(mContext); } if (title != null) { mRingtoneTitleCache.putString(uri.toString(), title); } Loading src/com/android/deskclock/AlarmUtils.java +27 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,15 @@ package com.android.deskclock; import android.Manifest; import android.annotation.TargetApi; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.TimePickerDialog; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.support.annotation.VisibleForTesting; import android.text.format.DateFormat; Loading Loading @@ -117,6 +120,30 @@ public class AlarmUtils { timePickerFragment.show(manager, FRAG_TAG_TIME_PICKER); } /** * @return {@code true} iff the user has granted permission to read the ringtone at the given * uri or no permission is required to read the ringtone */ public static boolean hasPermissionToDisplayRingtoneTitle(Context context, Uri ringtoneUri) { final PackageManager pm = context.getPackageManager(); final String packageName = context.getPackageName(); // If no ringtone is specified, return true. if (ringtoneUri == null || ringtoneUri == Alarm.NO_RINGTONE_URI) { return true; } // If the permission is already granted, return true. if (pm.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, packageName) == PackageManager.PERMISSION_GRANTED) { return true; } // If the ringtone is internal, return true; // external ringtones require the permission to see their title return ringtoneUri.toString().startsWith("content://media/internal/"); } /** * format "Alarm set for 2 days, 7 hours, and 53 minutes from now." */ Loading Loading
AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ <!-- READ_PHONE_STATE is required to determine when a phone call exists prior to M --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- READ_EXTERNAL_STORAGE is required to play custom ringtones from the SD card prior to M --> <!-- It is also required to display user-friendly names for custom ringtones in the UI. --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:label="@string/app_label" Loading
res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,9 @@ <!-- Setting labels on Set alarm screen: Select alarm ringtone --> <string name="alert">Alarm Ringtone</string> <!-- Label on expanded alarm edit view indicating the ringtone is custom. --> <string name="custom_ringtone">Custom Ringtone</string> <!-- Label on expanded alarm edit view. --> <string name="ringtone">Ringtone</string> Loading
src/com/android/deskclock/AlarmClockFragment.java +30 −6 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.deskclock; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; Loading Loading @@ -105,6 +106,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements private static final String KEY_SELECTED_ALARM = "selectedAlarm"; private static final int REQUEST_CODE_RINGTONE = 1; private static final int REQUEST_CODE_PERMISSIONS = 2; private static final long INVALID_ID = -1; private static final String PREF_KEY_DEFAULT_ALARM_RINGTONE_URI = "default_alarm_ringtone_uri"; Loading Loading @@ -473,6 +475,13 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements setDefaultRingtoneUri(uri); asyncUpdateAlarm(mSelectedAlarm, false); // If the user chose an external ringtone and has not yet granted the permission to read // external storage, ask them for that permission now. if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(getActivity(), uri)) { final String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE}; requestPermissions(perms, REQUEST_CODE_PERMISSIONS); } } private Uri getDefaultRingtoneUri() { Loading Loading @@ -512,6 +521,14 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { // The permission change may alter the cached ringtone titles so clear them. // (e.g. READ_EXTERNAL_STORAGE is granted or revoked) mRingtoneTitleCache.clear(); } private class AlarmItemAdapter extends CursorAdapter { private final Context mContext; private final LayoutInflater mFactory; Loading Loading @@ -1083,13 +1100,20 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements // Try the cache first String title = mRingtoneTitleCache.getString(uri.toString()); if (title == null) { // If the user cannot read the ringtone file, insert our own name rather than the // ugly one returned by Ringtone.getTitle(). if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(mContext, uri)) { title = getString(R.string.custom_ringtone); } else { // This is slow because a media player is created during Ringtone object creation. Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); final Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); if (ringTone == null) { LogUtils.i("No ringtone for uri %s", uri.toString()); return null; } title = ringTone.getTitle(mContext); } if (title != null) { mRingtoneTitleCache.putString(uri.toString(), title); } Loading
src/com/android/deskclock/AlarmUtils.java +27 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,15 @@ package com.android.deskclock; import android.Manifest; import android.annotation.TargetApi; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.TimePickerDialog; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.support.annotation.VisibleForTesting; import android.text.format.DateFormat; Loading Loading @@ -117,6 +120,30 @@ public class AlarmUtils { timePickerFragment.show(manager, FRAG_TAG_TIME_PICKER); } /** * @return {@code true} iff the user has granted permission to read the ringtone at the given * uri or no permission is required to read the ringtone */ public static boolean hasPermissionToDisplayRingtoneTitle(Context context, Uri ringtoneUri) { final PackageManager pm = context.getPackageManager(); final String packageName = context.getPackageName(); // If no ringtone is specified, return true. if (ringtoneUri == null || ringtoneUri == Alarm.NO_RINGTONE_URI) { return true; } // If the permission is already granted, return true. if (pm.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, packageName) == PackageManager.PERMISSION_GRANTED) { return true; } // If the ringtone is internal, return true; // external ringtones require the permission to see their title return ringtoneUri.toString().startsWith("content://media/internal/"); } /** * format "Alarm set for 2 days, 7 hours, and 53 minutes from now." */ Loading