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 if (mCurrentRingtone != null) { 97 mCurrentRingtone.play(); 98 mUiHandler.postDelayed(() -> { 99 if (mCurrentRingtone.isPlaying()) { 100 mCurrentRingtone.stop(); 101 mCurrentRingtone = null; 102 } 103 }, AUDIO_FEEDBACK_DURATION_MS); 104 } 105 } 106 107 /** Stop playing the current ringtone. */ stopCurrentRingtone()108 public void stopCurrentRingtone() { 109 if (mCurrentRingtone != null) { 110 mCurrentRingtone.stop(); 111 } 112 } 113 114 /** If we have already seen this ringtone, use it. Otherwise load when requested. */ lazyLoadRingtone(int group, int usage)115 private Ringtone lazyLoadRingtone(int group, int usage) { 116 if (!mGroupToRingtoneMap.containsKey(group)) { 117 Ringtone ringtone = RingtoneManager.getRingtone(mContext, getRingtoneUri(usage)); 118 if (ringtone == null) { 119 return null; 120 } 121 122 AudioAttributes.Builder builder = new AudioAttributes.Builder(); 123 if (AudioAttributes.isSystemUsage(usage)) { 124 builder.setSystemUsage(usage); 125 } else { 126 builder.setUsage(usage); 127 } 128 129 ringtone.setAudioAttributes(builder.build()); 130 mGroupToRingtoneMap.put(group, ringtone); 131 } 132 return mGroupToRingtoneMap.get(group); 133 } 134 135 // TODO: bundle car-specific audio sample assets in res/raw by usage 136 @VisibleForTesting getRingtoneUri(@udioAttributes.AttributeUsage int usage)137 Uri getRingtoneUri(@AudioAttributes.AttributeUsage int usage) { 138 return mAttributeUsageToRingtoneUriMap.getOrDefault( 139 usage, Settings.System.DEFAULT_RINGTONE_URI); 140 } 141 } 142