blob: 7261d1ef7d304a0180de0a13cbed5551694daa2a [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.incallui.ringtone;
import android.media.AudioManager;
import android.media.ToneGenerator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.incallui.Log;
import com.android.incallui.async.PausableExecutor;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Class responsible for playing in-call related tones in a background thread. This class only
* allows one tone to be played at a time.
*/
public class InCallTonePlayer {
public static final int TONE_CALL_WAITING = 4;
public static final int VOLUME_RELATIVE_HIGH_PRIORITY = 80;
@NonNull
private final ToneGeneratorFactory toneGeneratorFactory;
@NonNull private final PausableExecutor executor;
private @Nullable CountDownLatch numPlayingTones;
/**
* Creates a new InCallTonePlayer.
*
* @param toneGeneratorFactory the {@link ToneGeneratorFactory} used to create {@link
* ToneGenerator}s.
* @param executor the {@link PausableExecutor} used to play tones in a background thread.
* @throws NullPointerException if audioModeProvider, toneGeneratorFactory, or executor are {@code
* null}.
*/
public InCallTonePlayer(
@NonNull ToneGeneratorFactory toneGeneratorFactory, @NonNull PausableExecutor executor) {
this.toneGeneratorFactory = Objects.requireNonNull(toneGeneratorFactory);
this.executor = Objects.requireNonNull(executor);
}
/** @return {@code true} if a tone is currently playing, {@code false} otherwise. */
public boolean isPlayingTone() {
return numPlayingTones != null && numPlayingTones.getCount() > 0;
}
/**
* Plays the given tone in a background thread.
*
* @param tone the tone to play.
* @throws IllegalStateException if a tone is already playing.
* @throws IllegalArgumentException if the tone is invalid.
*/
public void play(int tone) {
if (isPlayingTone()) {
throw new IllegalStateException("Tone already playing");
}
final ToneGeneratorInfo info = getToneGeneratorInfo(tone);
numPlayingTones = new CountDownLatch(1);
executor.execute(
new Runnable() {
@Override
public void run() {
playOnBackgroundThread(info);
}
});
}
private ToneGeneratorInfo getToneGeneratorInfo(int tone) {
switch (tone) {
case TONE_CALL_WAITING:
/*
* DialerCall waiting tones play until they're stopped either by the user accepting or
* declining the call so the tone length is set at what's effectively forever. The
* tone is played at a high priority volume and through STREAM_VOICE_CALL since it's
* call related and using that stream will route it through bluetooth devices
* appropriately.
*/
return new ToneGeneratorInfo(
ToneGenerator.TONE_SUP_CALL_WAITING,
VOLUME_RELATIVE_HIGH_PRIORITY,
Integer.MAX_VALUE,
AudioManager.STREAM_VOICE_CALL);
default:
throw new IllegalArgumentException("Bad tone: " + tone);
}
}
private void playOnBackgroundThread(ToneGeneratorInfo info) {
ToneGenerator toneGenerator = null;
try {
Log.v(this, "Starting tone " + info);
toneGenerator = toneGeneratorFactory.newInCallToneGenerator(info.stream, info.volume);
toneGenerator.startTone(info.tone);
/*
* During tests, this will block until the tests call mExecutor.ackMilestone. This call
* allows for synchronization to the point where the tone has started playing.
*/
executor.milestone();
if (numPlayingTones != null) {
numPlayingTones.await(info.toneLengthMillis, TimeUnit.MILLISECONDS);
// Allows for synchronization to the point where the tone has completed playing.
executor.milestone();
}
} catch (InterruptedException e) {
Log.w(this, "Interrupted while playing in-call tone.");
} finally {
if (toneGenerator != null) {
toneGenerator.release();
}
if (numPlayingTones != null) {
numPlayingTones.countDown();
}
// Allows for synchronization to the point where this background thread has cleaned up.
executor.milestone();
}
}
/** Stops playback of the current tone. */
public void stop() {
if (numPlayingTones != null) {
numPlayingTones.countDown();
}
}
private static class ToneGeneratorInfo {
public final int tone;
public final int volume;
public final int toneLengthMillis;
public final int stream;
public ToneGeneratorInfo(int toneGeneratorType, int volume, int toneLengthMillis, int stream) {
this.tone = toneGeneratorType;
this.volume = volume;
this.toneLengthMillis = toneLengthMillis;
this.stream = stream;
}
@Override
public String toString() {
return "ToneGeneratorInfo{"
+ "toneLengthMillis="
+ toneLengthMillis
+ ", tone="
+ tone
+ ", volume="
+ volume
+ '}';
}
}
}