From 833b169eeb028628e339a742d52b68cd4c612e88 Mon Sep 17 00:00:00 2001 From: Edgar Arriaga Date: Wed, 7 Sep 2022 12:22:10 -0700 Subject: [PATCH] Reuse ringtone across multiple ringer operations This patch avoids multiple ringtone loads when taking place that were redundant as previous approach instantiated the same ringtone multiple times over the play flow instead we gather all the requirements for a ringtone and instantiate it once. Bug: 240621827 Test: atest RingtoneTest Test: atest RingerTest Test: atest IncomingCallTest Test: Verified ringtone and vibration works in various forms when receiving call. Change-Id: I7ac0a52c99d48fb7241c55c709a9a876a6057319 --- media/java/android/media/Ringtone.java | 40 +++++++++-- media/java/android/media/RingtoneManager.java | 69 ++++++++++--------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 82c3139bb4d33..538e64cf095cb 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -158,13 +158,15 @@ public class Ringtone { /** * Creates a local media player for the ringtone using currently set attributes. + * @return true if media player creation succeeded or is deferred, + * false if it did not succeed and can't be tried remotely. * @hide */ - public void createLocalMediaPlayer() { + public boolean createLocalMediaPlayer() { Trace.beginSection("createLocalMediaPlayer"); if (mUri == null) { Log.e(TAG, "Could not create media player as no URI was provided."); - return; + return mAllowRemote && mRemotePlayer != null; } destroyLocalPlayer(); // try opening uri locally before delegating to remote player @@ -195,6 +197,30 @@ public class Ringtone { } } Trace.endSection(); + return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null); + } + + /** + * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. + * If the ringtone has not been created, it will load based on URI provided at {@link #setUri} + * and if not URI has been set, it will assume no haptic channels are present. + * @hide + */ + public boolean hasHapticChannels() { + // FIXME: support remote player, or internalize haptic channels support and remove entirely. + try { + android.os.Trace.beginSection("Ringtone.hasHapticChannels"); + if (mLocalPlayer != null) { + for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) { + if (trackInfo.hasHapticChannels()) { + return true; + } + } + } + } finally { + android.os.Trace.endSection(); + } + return false; } /** @@ -423,7 +449,6 @@ public class Ringtone { */ public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) { mVolumeShaperConfig = volumeShaperConfig; - mUri = uri; if (mUri == null) { destroyLocalPlayer(); @@ -443,10 +468,11 @@ public class Ringtone { if (mLocalPlayer != null) { // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone // (typically because ringer mode is vibrate). - boolean isHapticOnly = AudioManager.hasHapticChannels(mContext, mUri) - && !mAudioAttributes.areHapticChannelsMuted() && mVolume == 0; - if (isHapticOnly || mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) { + if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) + != 0) { + startLocalPlayer(); + } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) { + // is haptic only ringtone startLocalPlayer(); } } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 27db41cb9f4e5..f15f4438d671f 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -720,11 +720,14 @@ public class RingtoneManager { @Nullable VolumeShaper.Configuration volumeShaperConfig, AudioAttributes audioAttributes) { // Don't set the stream type - Ringtone ringtone = - getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, false); + Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */, + volumeShaperConfig, false); if (ringtone != null) { ringtone.setAudioAttributesField(audioAttributes); - ringtone.createLocalMediaPlayer(); + if (!ringtone.createLocalMediaPlayer()) { + Log.e(TAG, "Failed to open ringtone " + ringtoneUri); + return null; + } } return ringtone; } @@ -750,19 +753,6 @@ public class RingtoneManager { createLocalMediaPlayer); } - //FIXME bypass the notion of stream types within the class - /** - * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on - * the given stream type. Normally, if you change the stream type on the returned - * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just - * an optimized route to avoid that. - * - * @param streamType The stream type for the ringtone, or -1 if it should - * not be set (and the default used instead). - * @param volumeShaperConfig config for volume shaper of the ringtone if applied. - * @see #getRingtone(Context, Uri) - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean createLocalMediaPlayer) { @@ -776,7 +766,10 @@ public class RingtoneManager { r.setVolumeShaperConfig(volumeShaperConfig); r.setUri(ringtoneUri, volumeShaperConfig); if (createLocalMediaPlayer) { - r.createLocalMediaPlayer(); + if (!r.createLocalMediaPlayer()) { + Log.e(TAG, "Failed to open ringtone " + ringtoneUri); + return null; + } } return r; } catch (Exception ex) { @@ -1158,24 +1151,38 @@ public class RingtoneManager { } // Try finding the scanned ringtone - final String filename = getDefaultRingtoneFilename(type); - final String whichAudio = getQueryStringForType(type); - final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?"; - final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; - try (Cursor cursor = context.getContentResolver().query(baseUri, - new String[] { MediaColumns._ID }, - where, - new String[] { filename, "1" }, null)) { - if (cursor.moveToFirst()) { - final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( - ContentUris.withAppendedId(baseUri, cursor.getLong(0))); - RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri); - Settings.System.putInt(context.getContentResolver(), setting, 1); - } + Uri ringtoneUri = computeDefaultRingtoneUri(context, type); + if (ringtoneUri != null) { + RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri); + Settings.System.putInt(context.getContentResolver(), setting, 1); } } } + /** + * @param type the type of ringtone (e.g {@link #TYPE_RINGTONE}) + * @return the system default URI if found, null otherwise. + */ + private static Uri computeDefaultRingtoneUri(@NonNull Context context, int type) { + // Try finding the scanned ringtone + final String filename = getDefaultRingtoneFilename(type); + final String whichAudio = getQueryStringForType(type); + final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?"; + final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; + try (Cursor cursor = context.getContentResolver().query(baseUri, + new String[] { MediaColumns._ID }, + where, + new String[] { filename, "1" }, null)) { + if (cursor.moveToFirst()) { + final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( + ContentUris.withAppendedId(baseUri, cursor.getLong(0))); + return ringtoneUri; + } + } + + return null; + } + private static String getDefaultRingtoneSetting(int type) { switch (type) { case TYPE_RINGTONE: return "ringtone_set";