Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5b762a5c authored by James Lemieux's avatar James Lemieux
Browse files

Prompt for READ_EXTERNAL_STORAGE perm on custom ringtone selection

If a ringtone stored on external storage (e.g. sd card) is selected
and the READ_EXTERNAL_STORAGE permission has not yet been granted
by the user, prompt the user to grant it. This prompt will be
displayed after the ringtone chooser is hidden if the selected
ringtone is external.

Bug: 20273223
Change-Id: I1c52aaaf3b36a279202c3d210a4f204637f43e53
parent de765346
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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"
+3 −0
Original line number Diff line number Diff line
@@ -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>

+30 −6
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.deskclock;

import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -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";

@@ -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() {
@@ -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;
@@ -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);
                }
+27 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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."
     */