summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Lemieux <jplemieux@google.com>2015-06-26 13:53:55 -0700
committerJames Lemieux <jplemieux@google.com>2015-06-26 15:42:38 -0700
commit95559b2952cb1f1492355f0ab572d40b8a2d000e (patch)
treeadf5723d753c584f07bf5c1fca7225466aee5830
parentac260c0096605526f772af7eec73d6a51dc6de32 (diff)
downloadandroid_packages_apps_DeskClock-95559b2952cb1f1492355f0ab572d40b8a2d000e.tar.gz
android_packages_apps_DeskClock-95559b2952cb1f1492355f0ab572d40b8a2d000e.tar.bz2
android_packages_apps_DeskClock-95559b2952cb1f1492355f0ab572d40b8a2d000e.zip
Fix remaining dangerous M permissions
android.permission.WRITE_SETTINGS was required to adjust the Setting that stored the last selected alarm ringtone as the new default. The value is now stored in SharedPreferences and no longer written to the Setting. The permission has been removed. android.permission.READ_EXTERNAL_STORAGE was required to play custom ringtones located at /sdcard/Alarms via MediaPlayer. MediaPlayer is no longer used to play those ringtones so the permission has been removed. Ringtone, which is designed to play custom ringtones without requiring the permission to read from external storage is now the playback method. The caveat to this approach is: Ringtone does not offer control over the volume at which the ringtone is played. Old MediaPlayer code used to detect when we were in a phone call and reduce the volume defensively to avoid playing a loud ringtone directly into a user's ear. In practice, it appears that lower layers of the audio stack are already handling this behavior and the app need not request a lower volume itself. Bug: 20273223 Change-Id: I328d168ff7677506aeae3fdf78c915f82e6cc1c7
-rw-r--r--AndroidManifest.xml3
-rw-r--r--src/com/android/deskclock/AlarmClockFragment.java57
-rw-r--r--src/com/android/deskclock/AsyncRingtonePlayer.java163
-rw-r--r--src/com/android/deskclock/alarms/AlarmKlaxon.java113
-rw-r--r--src/com/android/deskclock/alarms/AlarmService.java3
5 files changed, 217 insertions, 122 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2454a7941..c1a7c82ae 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,10 +27,9 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
- <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <!-- READ_PHONE_STATE is required to determine when a phone call exists prior to M -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application android:label="@string/app_label"
android:name=".DeskClockApplication"
diff --git a/src/com/android/deskclock/AlarmClockFragment.java b/src/com/android/deskclock/AlarmClockFragment.java
index 085c6f346..22969cf9c 100644
--- a/src/com/android/deskclock/AlarmClockFragment.java
+++ b/src/com/android/deskclock/AlarmClockFragment.java
@@ -27,6 +27,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
@@ -41,6 +42,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
+import android.preference.PreferenceManager;
import android.support.v4.view.ViewCompat;
import android.transition.AutoTransition;
import android.transition.Fade;
@@ -104,6 +106,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
private static final int REQUEST_CODE_RINGTONE = 1;
private static final long INVALID_ID = -1;
+ private static final String PREF_KEY_DEFAULT_ALARM_RINGTONE_URI = "default_alarm_ringtone_uri";
// Use transitions only in API 21+
private static final boolean USE_TRANSITION_FRAMEWORK =
@@ -153,8 +156,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
if (mSelectedAlarm == null) {
// If mSelectedAlarm is null then we're creating a new alarm.
Alarm a = new Alarm();
- a.alert = RingtoneManager.getActualDefaultRingtoneUri(getActivity(),
- RingtoneManager.TYPE_ALARM);
+ a.alert = getDefaultRingtoneUri();
if (a.alert == null) {
a.alert = Uri.parse("content://settings/system/alarm_alert");
}
@@ -288,14 +290,6 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
return v;
}
- private void setUndoBarRightMargin(int margin) {
- FrameLayout.LayoutParams params =
- (FrameLayout.LayoutParams) mUndoBar.getLayoutParams();
- ((FrameLayout.LayoutParams) mUndoBar.getLayoutParams())
- .setMargins(params.leftMargin, params.topMargin, margin, params.bottomMargin);
- mUndoBar.requestLayout();
- }
-
@Override
public void onResume() {
super.onResume();
@@ -476,13 +470,35 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
mSelectedAlarm.alert = uri;
// Save the last selected ringtone as the default for new alarms
- if (!Alarm.NO_RINGTONE_URI.equals(uri)) {
- RingtoneManager.setActualDefaultRingtoneUri(
- getActivity(), RingtoneManager.TYPE_ALARM, uri);
- }
+ setDefaultRingtoneUri(uri);
+
asyncUpdateAlarm(mSelectedAlarm, false);
}
+ private Uri getDefaultRingtoneUri() {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ final String ringtoneUriString = sp.getString(PREF_KEY_DEFAULT_ALARM_RINGTONE_URI, null);
+
+ final Uri ringtoneUri;
+ if (ringtoneUriString != null) {
+ ringtoneUri = Uri.parse(ringtoneUriString);
+ } else {
+ ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(getActivity(),
+ RingtoneManager.TYPE_ALARM);
+ }
+
+ return ringtoneUri;
+ }
+
+ private void setDefaultRingtoneUri(Uri uri) {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ if (uri == null) {
+ sp.edit().remove(PREF_KEY_DEFAULT_ALARM_RINGTONE_URI).apply();
+ } else {
+ sp.edit().putString(PREF_KEY_DEFAULT_ALARM_RINGTONE_URI, uri.toString()).apply();
+ }
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
@@ -504,8 +520,8 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
private long mExpandedId;
private ItemHolder mExpandedItemHolder;
- private final HashSet<Long> mRepeatChecked = new HashSet<Long>();
- private final HashSet<Long> mSelectedAlarms = new HashSet<Long>();
+ private final HashSet<Long> mRepeatChecked = new HashSet<>();
+ private final HashSet<Long> mSelectedAlarms = new HashSet<>();
private Bundle mPreviousDaysOfWeekMap = new Bundle();
private final boolean mHasVibrator;
@@ -597,10 +613,6 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
setDayOrder();
}
- public void removeSelectedId(int id) {
- mSelectedAlarms.remove(id);
- }
-
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (!getCursor().moveToPosition(position)) {
@@ -988,8 +1000,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
itemHolder.vibrate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final boolean checked = ((CheckBox) v).isChecked();
- alarm.vibrate = checked;
+ alarm.vibrate = ((CheckBox) v).isChecked();
asyncUpdateAlarm(alarm, false);
}
});
@@ -1060,7 +1071,7 @@ public abstract class AlarmClockFragment extends DeskClockFragment implements
Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri);
if (ringTone == null) {
LogUtils.i("No ringtone for uri %s", uri.toString());
- return title;
+ return null;
}
title = ringTone.getTitle(mContext);
if (title != null) {
diff --git a/src/com/android/deskclock/AsyncRingtonePlayer.java b/src/com/android/deskclock/AsyncRingtonePlayer.java
new file mode 100644
index 000000000..fac5137f2
--- /dev/null
+++ b/src/com/android/deskclock/AsyncRingtonePlayer.java
@@ -0,0 +1,163 @@
+package com.android.deskclock;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Plays the alarm ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
+ * used from the main thread. Consequently, problems controlling the ringtone do not cause ANRs in
+ * the main thread of the application.
+ */
+public class AsyncRingtonePlayer {
+
+ private static final String TAG = "AsyncRingtonePlayer";
+
+ // Message codes used with the ringtone thread.
+ private static final int EVENT_PLAY = 1;
+ private static final int EVENT_STOP = 2;
+ private static final String RINGTONE_URI_KEY = "RINGTONE_URI_KEY";
+
+ /** Handler running on the ringtone thread. */
+ private Handler mHandler;
+
+ /** The audio focus manager. Only used by the ringtone thread. */
+ private AudioManager mAudioManager;
+
+ /** The current ringtone. Only used by the ringtone thread. */
+ private Ringtone mRingtone;
+
+ /** The context. */
+ private final Context mContext;
+
+ public AsyncRingtonePlayer(Context context) {
+ mContext = context;
+ }
+
+ /** Plays the ringtone. */
+ public void play(Uri ringtoneUri) {
+ LogUtils.d(TAG, "Posting play.");
+ postMessage(EVENT_PLAY, ringtoneUri);
+ }
+
+ /** Stops playing the ringtone. */
+ public void stop() {
+ LogUtils.d(TAG, "Posting stop.");
+ postMessage(EVENT_STOP, null);
+ }
+
+ /**
+ * Posts a message to the ringtone-thread handler.
+ *
+ * @param messageCode The message to post.
+ */
+ private void postMessage(int messageCode, Uri ringtoneUri) {
+ synchronized (this) {
+ if (mHandler == null) {
+ mHandler = getNewHandler();
+ }
+
+ final Message message = mHandler.obtainMessage(messageCode);
+ if (ringtoneUri != null) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(RINGTONE_URI_KEY, ringtoneUri);
+ message.setData(bundle);
+ }
+ message.sendToTarget();
+ }
+ }
+
+ /**
+ * Creates a new ringtone Handler running in its own thread.
+ */
+ private Handler getNewHandler() {
+ final HandlerThread thread = new HandlerThread("ringtone-player");
+ thread.start();
+
+ return new Handler(thread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_PLAY:
+ final Uri ringtoneUri = msg.getData().getParcelable(RINGTONE_URI_KEY);
+ handlePlay(ringtoneUri);
+ break;
+ case EVENT_STOP:
+ handleStop();
+ break;
+ }
+ }
+ };
+ }
+
+ /**
+ * Starts the actual playback of the ringtone. Executes on ringtone-thread.
+ */
+ private void handlePlay(Uri ringtoneUri) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ LogUtils.e(TAG, "Must not be on the main thread!", new IllegalStateException());
+ }
+
+ LogUtils.i(TAG, "Play ringtone.");
+
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ // attempt to fetch the specified ringtone
+ mRingtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
+
+ if (mRingtone == null) {
+ // fall back to the default ringtone
+ final Uri alarmRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ mRingtone = RingtoneManager.getRingtone(mContext, alarmRingtoneUri);
+ }
+
+ // if we don't have a ringtone at this point there isn't much recourse
+ if (mRingtone == null) {
+ LogUtils.i(TAG, "Unable to locate alarm ringtone.");
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mRingtone.setAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build());
+ }
+
+ mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ mRingtone.play();
+ }
+
+ /**
+ * Stops the playback of the ringtone. Executes on the ringtone-thread.
+ */
+ private void handleStop() {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ LogUtils.e(TAG, "Must not be on the main thread!", new IllegalStateException());
+ }
+
+ LogUtils.i(TAG, "Stop ringtone.");
+
+ if (mRingtone != null && mRingtone.isPlaying()) {
+ LogUtils.d(TAG, "Ringtone.stop() invoked.");
+ mRingtone.stop();
+ }
+
+ if (mAudioManager != null) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+}
+
diff --git a/src/com/android/deskclock/alarms/AlarmKlaxon.java b/src/com/android/deskclock/alarms/AlarmKlaxon.java
index e5401ab77..c286f459b 100644
--- a/src/com/android/deskclock/alarms/AlarmKlaxon.java
+++ b/src/com/android/deskclock/alarms/AlarmKlaxon.java
@@ -17,112 +17,50 @@
package com.android.deskclock.alarms;
import android.content.Context;
-import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.RingtoneManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Vibrator;
+import com.android.deskclock.AsyncRingtonePlayer;
import com.android.deskclock.LogUtils;
-import com.android.deskclock.R;
import com.android.deskclock.provider.AlarmInstance;
-import java.io.IOException;
-
/**
* Manages playing ringtone and vibrating the device.
*/
-public class AlarmKlaxon {
- private static final long[] sVibratePattern = new long[] { 500, 500 };
-
- // Volume suggested by media team for in-call alarms.
- private static final float IN_CALL_VOLUME = 0.125f;
+public final class AlarmKlaxon {
+ private static final long[] sVibratePattern = {500, 500};
private static boolean sStarted = false;
- private static MediaPlayer sMediaPlayer = null;
+ private static AsyncRingtonePlayer sAsyncRingtonePlayer;
+
+ private AlarmKlaxon() {}
public static void stop(Context context) {
LogUtils.v("AlarmKlaxon.stop()");
if (sStarted) {
sStarted = false;
- // Stop audio playing
- if (sMediaPlayer != null) {
- sMediaPlayer.stop();
- AudioManager audioManager = (AudioManager)
- context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.abandonAudioFocus(null);
- sMediaPlayer.release();
- sMediaPlayer = null;
- }
-
- ((Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE)).cancel();
+ getAsyncRingtonePlayer(context).stop();
+ ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).cancel();
}
}
- public static void start(final Context context, AlarmInstance instance,
- boolean inTelephoneCall) {
+ public static void start(Context context, AlarmInstance instance) {
LogUtils.v("AlarmKlaxon.start()");
- // Make sure we are stop before starting
+ // Make sure we are stopped before starting
stop(context);
if (!AlarmInstance.NO_RINGTONE_URI.equals(instance.mRingtone)) {
- Uri alarmNoise = instance.mRingtone;
- // Fall back on the default alarm if the database does not have an
- // alarm stored.
- if (alarmNoise == null) {
- alarmNoise = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
- LogUtils.v("Using default alarm: " + alarmNoise.toString());
- }
-
- // TODO: Reuse mMediaPlayer instead of creating a new one and/or use RingtoneManager.
- sMediaPlayer = new MediaPlayer();
- sMediaPlayer.setOnErrorListener(new OnErrorListener() {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- LogUtils.e("Error occurred while playing audio. Stopping AlarmKlaxon.");
- AlarmKlaxon.stop(context);
- return true;
- }
- });
-
- try {
- // Check if we are in a call. If we are, use the in-call alarm
- // resource at a low volume to not disrupt the call.
- if (inTelephoneCall) {
- LogUtils.v("Using the in-call alarm");
- sMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
- setDataSourceFromResource(context, sMediaPlayer, R.raw.in_call_alarm);
- } else {
- sMediaPlayer.setDataSource(context, alarmNoise);
- }
- startAlarm(context, sMediaPlayer);
- } catch (Exception ex) {
- LogUtils.e("Use the fallback ringtone, original was " + alarmNoise, ex);
- // The alarmNoise may be on the sd card which could be busy right
- // now. Use the fallback ringtone.
- try {
- // Must reset the media player to clear the error state.
- sMediaPlayer.reset();
- setDataSourceFromResource(context, sMediaPlayer, R.raw.fallbackring);
- startAlarm(context, sMediaPlayer);
- } catch (Exception ex2) {
- // At this point we just don't play anything.
- LogUtils.e("Failed to play fallback ringtone", ex2);
- }
- }
+ getAsyncRingtonePlayer(context).play(instance.mRingtone);
}
if (instance.mVibrate) {
- Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
vibrator.vibrate(sVibratePattern, 0, new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build());
} else {
vibrator.vibrate(sVibratePattern, 0);
@@ -132,26 +70,11 @@ public class AlarmKlaxon {
sStarted = true;
}
- // Do the common stuff when starting the alarm.
- private static void startAlarm(Context context, MediaPlayer player) throws IOException {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- // do not play alarms if stream volume is 0 (typically because ringer mode is silent).
- if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
- player.setAudioStreamType(AudioManager.STREAM_ALARM);
- player.setLooping(true);
- player.prepare();
- audioManager.requestAudioFocus(null,
- AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- player.start();
+ private static synchronized AsyncRingtonePlayer getAsyncRingtonePlayer(Context context) {
+ if (sAsyncRingtonePlayer == null) {
+ sAsyncRingtonePlayer = new AsyncRingtonePlayer(context.getApplicationContext());
}
- }
- private static void setDataSourceFromResource(Context context, MediaPlayer player, int res)
- throws IOException {
- AssetFileDescriptor afd = context.getResources().openRawResourceFd(res);
- if (afd != null) {
- player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- afd.close();
- }
+ return sAsyncRingtonePlayer;
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/deskclock/alarms/AlarmService.java b/src/com/android/deskclock/alarms/AlarmService.java
index 16018b756..3c37f7095 100644
--- a/src/com/android/deskclock/alarms/AlarmService.java
+++ b/src/com/android/deskclock/alarms/AlarmService.java
@@ -150,8 +150,7 @@ public class AlarmService extends Service {
AlarmNotifications.showAlarmNotification(this, mCurrentAlarm);
mInitialCallState = mTelephonyManager.getCallState();
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- final boolean inCall = mInitialCallState != TelephonyManager.CALL_STATE_IDLE;
- AlarmKlaxon.start(this, mCurrentAlarm, inCall);
+ AlarmKlaxon.start(this, mCurrentAlarm);
sendBroadcast(new Intent(ALARM_ALERT_ACTION));
}