1 /* 2 * Copyright (C) 2016 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.incallui.ringtone; 18 19 import com.google.common.base.MoreObjects; 20 import com.google.common.base.Preconditions; 21 22 import android.media.AudioManager; 23 import android.media.ToneGenerator; 24 import android.support.annotation.Nullable; 25 26 import com.android.incallui.Log; 27 import com.android.incallui.async.PausableExecutor; 28 29 import java.util.concurrent.CountDownLatch; 30 import java.util.concurrent.TimeUnit; 31 32 /** 33 * Class responsible for playing in-call related tones in a background thread. This class only 34 * allows one tone to be played at a time. 35 */ 36 public class InCallTonePlayer { 37 38 public static final int TONE_CALL_WAITING = 4; 39 40 public static final int VOLUME_RELATIVE_HIGH_PRIORITY = 80; 41 42 private final ToneGeneratorFactory mToneGeneratorFactory; 43 private final PausableExecutor mExecutor; 44 private @Nullable CountDownLatch mNumPlayingTones; 45 46 /** 47 * Creates a new InCallTonePlayer. 48 * 49 * @param toneGeneratorFactory the {@link ToneGeneratorFactory} used to create 50 * {@link ToneGenerator}s. 51 * @param executor the {@link PausableExecutor} used to play tones in a background thread. 52 * @throws NullPointerException if audioModeProvider, toneGeneratorFactory, or executor are 53 * {@code null}. 54 */ InCallTonePlayer(ToneGeneratorFactory toneGeneratorFactory, PausableExecutor executor)55 public InCallTonePlayer(ToneGeneratorFactory toneGeneratorFactory, PausableExecutor executor) { 56 mToneGeneratorFactory = Preconditions.checkNotNull(toneGeneratorFactory); 57 mExecutor = Preconditions.checkNotNull(executor); 58 } 59 60 /** 61 * @return {@code true} if a tone is currently playing, {@code false} otherwise. 62 */ isPlayingTone()63 public boolean isPlayingTone() { 64 return mNumPlayingTones != null && mNumPlayingTones.getCount() > 0; 65 } 66 67 /** 68 * Plays the given tone in a background thread. 69 * 70 * @param tone the tone to play. 71 * @throws IllegalStateException if a tone is already playing. 72 * @throws IllegalArgumentException if the tone is invalid. 73 */ play(int tone)74 public void play(int tone) { 75 if (isPlayingTone()) { 76 throw new IllegalStateException("Tone already playing"); 77 } 78 final ToneGeneratorInfo info = getToneGeneratorInfo(tone); 79 mNumPlayingTones = new CountDownLatch(1); 80 mExecutor.execute(new Runnable() { 81 @Override 82 public void run() { 83 playOnBackgroundThread(info); 84 } 85 }); 86 } 87 getToneGeneratorInfo(int tone)88 private ToneGeneratorInfo getToneGeneratorInfo(int tone) { 89 switch (tone) { 90 case TONE_CALL_WAITING: 91 /* 92 * Call waiting tones play until they're stopped either by the user accepting or 93 * declining the call so the tone length is set at what's effectively forever. The 94 * tone is played at a high priority volume and through STREAM_VOICE_CALL since it's 95 * call related and using that stream will route it through bluetooth devices 96 * appropriately. 97 */ 98 return new ToneGeneratorInfo(ToneGenerator.TONE_SUP_CALL_WAITING, 99 VOLUME_RELATIVE_HIGH_PRIORITY, 100 Integer.MAX_VALUE, 101 AudioManager.STREAM_VOICE_CALL); 102 default: 103 throw new IllegalArgumentException("Bad tone: " + tone); 104 } 105 } 106 playOnBackgroundThread(ToneGeneratorInfo info)107 private void playOnBackgroundThread(ToneGeneratorInfo info) { 108 ToneGenerator toneGenerator = null; 109 try { 110 Log.v(this, "Starting tone " + info); 111 toneGenerator = mToneGeneratorFactory.newInCallToneGenerator(info.stream, info.volume); 112 toneGenerator.startTone(info.tone); 113 /* 114 * During tests, this will block until the tests call mExecutor.ackMilestone. This call 115 * allows for synchronization to the point where the tone has started playing. 116 */ 117 mExecutor.milestone(); 118 if (mNumPlayingTones != null) { 119 mNumPlayingTones.await(info.toneLengthMillis, TimeUnit.MILLISECONDS); 120 // Allows for synchronization to the point where the tone has completed playing. 121 mExecutor.milestone(); 122 } 123 } catch (InterruptedException e) { 124 Log.w(this, "Interrupted while playing in-call tone."); 125 } finally { 126 if (toneGenerator != null) { 127 toneGenerator.release(); 128 } 129 if (mNumPlayingTones != null) { 130 mNumPlayingTones.countDown(); 131 } 132 // Allows for synchronization to the point where this background thread has cleaned up. 133 mExecutor.milestone(); 134 } 135 } 136 137 /** 138 * Stops playback of the current tone. 139 */ stop()140 public void stop() { 141 if (mNumPlayingTones != null) { 142 mNumPlayingTones.countDown(); 143 } 144 } 145 146 private static class ToneGeneratorInfo { 147 public final int tone; 148 public final int volume; 149 public final int toneLengthMillis; 150 public final int stream; 151 ToneGeneratorInfo(int toneGeneratorType, int volume, int toneLengthMillis, int stream)152 public ToneGeneratorInfo(int toneGeneratorType, int volume, int toneLengthMillis, 153 int stream) { 154 this.tone = toneGeneratorType; 155 this.volume = volume; 156 this.toneLengthMillis = toneLengthMillis; 157 this.stream = stream; 158 } 159 160 @Override toString()161 public String toString() { 162 return MoreObjects.toStringHelper(this) 163 .add("tone", tone) 164 .add("volume", volume) 165 .add("toneLengthMillis", toneLengthMillis).toString(); 166 } 167 } 168 } 169