1 /* 2 * Copyright (C) 2018 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.car.settings.sound; 18 19 import android.content.Context; 20 import android.media.AudioAttributes; 21 import android.media.Ringtone; 22 import android.media.RingtoneManager; 23 import android.net.Uri; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.provider.Settings; 27 28 import androidx.annotation.Nullable; 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.car.settings.R; 32 import com.android.car.settings.common.Logger; 33 34 import java.util.HashMap; 35 import java.util.Map; 36 37 /** Manges the audio played by the {@link VolumeSettingsPreferenceController}. */ 38 public class VolumeSettingsRingtoneManager { 39 40 private static final Logger LOG = new Logger(VolumeSettingsRingtoneManager.class); 41 42 private static final int AUDIO_FEEDBACK_DURATION_MS = 1000; 43 44 private final Context mContext; 45 private final Handler mUiHandler; 46 private final Map<Integer, Ringtone> mGroupToRingtoneMap = new HashMap<>(); 47 private final Map<Integer, Uri> mAttributeUsageToRingtoneUriMap = new HashMap<>(); 48 49 @Nullable 50 private Ringtone mCurrentRingtone; 51 VolumeSettingsRingtoneManager(Context context)52 public VolumeSettingsRingtoneManager(Context context) { 53 mContext = context; 54 mUiHandler = new Handler(Looper.getMainLooper()); 55 populateAttributeUsageToRingtoneUriMap(); 56 } 57 populateAttributeUsageToRingtoneUriMap()58 private void populateAttributeUsageToRingtoneUriMap() { 59 int[] audioAttributeUsages = mContext 60 .getResources() 61 .getIntArray(R.array.config_ringtone_audio_attribute_usages_map_key); 62 String[] ringtoneStrings = mContext 63 .getResources() 64 .getStringArray(R.array.config_ringtone_uri_map_value); 65 if (audioAttributeUsages.length != ringtoneStrings.length) { 66 throw new IllegalArgumentException( 67 "config_ringtone_audio_attribute_usages_map_key and config_ringtone_uri_map_value " 68 + "must have the same number of items"); 69 } 70 71 for (int i = 0; i < audioAttributeUsages.length; i++) { 72 @AudioAttributes.AttributeUsage int usage = audioAttributeUsages[i]; 73 if (AudioAttributes.usageToString(usage).contains("unknown usage")) { 74 throw new IllegalArgumentException( 75 "Invalid usage in config_ringtone_audio_attribute_usages_map_key"); 76 } 77 Uri ringtoneUri = Uri.parse(ringtoneStrings[i]); 78 LOG.d("Usage " + usage + " mapped to ringtone " + ringtoneUri); 79 mAttributeUsageToRingtoneUriMap.put(usage, ringtoneUri); 80 } 81 } 82 83 /** 84 * Play the audio defined by the current group and usage. Stop the current ringtone if it is a 85 * different ringtone than what is currently playing. 86 */ playAudioFeedback(int group, int usage)87 public void playAudioFeedback(int group, int usage) { 88 Ringtone nextRingtone = lazyLoadRingtone(group, usage); 89 if (mCurrentRingtone != null && mCurrentRingtone != nextRingtone 90 && mCurrentRingtone.isPlaying()) { 91 mCurrentRingtone.stop(); 92 } 93 94 mUiHandler.removeCallbacksAndMessages(null); 95 mCurrentRingtone = nextRingtone; 96 mCurrentRingtone.play(); 97 mUiHandler.postDelayed(() -> { 98 if (mCurrentRingtone.isPlaying()) { 99 mCurrentRingtone.stop(); 100 mCurrentRingtone = null; 101 } 102 }, AUDIO_FEEDBACK_DURATION_MS); 103 } 104 105 /** Stop playing the current ringtone. */ stopCurrentRingtone()106 public void stopCurrentRingtone() { 107 if (mCurrentRingtone != null) { 108 mCurrentRingtone.stop(); 109 } 110 } 111 112 /** If we have already seen this ringtone, use it. Otherwise load when requested. */ lazyLoadRingtone(int group, int usage)113 private Ringtone lazyLoadRingtone(int group, int usage) { 114 if (!mGroupToRingtoneMap.containsKey(group)) { 115 Ringtone ringtone = RingtoneManager.getRingtone(mContext, getRingtoneUri(usage)); 116 ringtone.setAudioAttributes(new AudioAttributes.Builder().setUsage(usage).build()); 117 mGroupToRingtoneMap.put(group, ringtone); 118 } 119 return mGroupToRingtoneMap.get(group); 120 } 121 122 // TODO: bundle car-specific audio sample assets in res/raw by usage 123 @VisibleForTesting getRingtoneUri(@udioAttributes.AttributeUsage int usage)124 Uri getRingtoneUri(@AudioAttributes.AttributeUsage int usage) { 125 return mAttributeUsageToRingtoneUriMap.getOrDefault( 126 usage, Settings.System.DEFAULT_RINGTONE_URI); 127 } 128 } 129