summaryrefslogtreecommitdiff
path: root/java/com/android/incallui/ringtone
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/incallui/ringtone')
-rw-r--r--java/com/android/incallui/ringtone/DialerRingtoneManager.java134
-rw-r--r--java/com/android/incallui/ringtone/InCallTonePlayer.java168
-rw-r--r--java/com/android/incallui/ringtone/ToneGeneratorFactory.java34
3 files changed, 336 insertions, 0 deletions
diff --git a/java/com/android/incallui/ringtone/DialerRingtoneManager.java b/java/com/android/incallui/ringtone/DialerRingtoneManager.java
new file mode 100644
index 000000000..5ebd93378
--- /dev/null
+++ b/java/com/android/incallui/ringtone/DialerRingtoneManager.java
@@ -0,0 +1,134 @@
+/*
+ * 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.content.ContentResolver;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall.State;
+import java.util.Objects;
+
+/**
+ * Class that determines when ringtones should be played and can play the call waiting tone when
+ * necessary.
+ */
+public class DialerRingtoneManager {
+
+ /*
+ * Flag used to determine if the Dialer is responsible for playing ringtones for incoming calls.
+ * Once we're ready to enable Dialer Ringing, these flags should be removed.
+ */
+ private static final boolean IS_DIALER_RINGING_ENABLED = false;
+ private final InCallTonePlayer mInCallTonePlayer;
+ private final CallList mCallList;
+ private Boolean mIsDialerRingingEnabledForTesting;
+
+ /**
+ * Creates the DialerRingtoneManager with the given {@link InCallTonePlayer}.
+ *
+ * @param inCallTonePlayer the tone player used to play in-call tones.
+ * @param callList the CallList used to check for {@link State#CALL_WAITING}
+ * @throws NullPointerException if inCallTonePlayer or callList are null
+ */
+ public DialerRingtoneManager(
+ @NonNull InCallTonePlayer inCallTonePlayer, @NonNull CallList callList) {
+ mInCallTonePlayer = Objects.requireNonNull(inCallTonePlayer);
+ mCallList = Objects.requireNonNull(callList);
+ }
+
+ /**
+ * Determines if a ringtone should be played for the given call state (see {@link State}) and
+ * {@link Uri}.
+ *
+ * @param callState the call state for the call being checked.
+ * @param ringtoneUri the ringtone to potentially play.
+ * @return {@code true} if the ringtone should be played, {@code false} otherwise.
+ */
+ public boolean shouldPlayRingtone(int callState, @Nullable Uri ringtoneUri) {
+ return isDialerRingingEnabled()
+ && translateCallStateForCallWaiting(callState) == State.INCOMING
+ && ringtoneUri != null;
+ }
+
+ /**
+ * Determines if an incoming call should vibrate as well as ring.
+ *
+ * @param resolver {@link ContentResolver} used to look up the {@link
+ * Settings.System#VIBRATE_WHEN_RINGING} setting.
+ * @return {@code true} if the call should vibrate, {@code false} otherwise.
+ */
+ public boolean shouldVibrate(ContentResolver resolver) {
+ return Settings.System.getInt(resolver, Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
+ }
+
+ /**
+ * The incoming callState is never set as {@link State#CALL_WAITING} because {@link
+ * DialerCall#translateState(int)} doesn't account for that case, check for it here
+ */
+ private int translateCallStateForCallWaiting(int callState) {
+ if (callState != State.INCOMING) {
+ return callState;
+ }
+ return mCallList.getActiveCall() == null ? State.INCOMING : State.CALL_WAITING;
+ }
+
+ private boolean isDialerRingingEnabled() {
+ boolean enabledFlag =
+ mIsDialerRingingEnabledForTesting != null
+ ? mIsDialerRingingEnabledForTesting
+ : IS_DIALER_RINGING_ENABLED;
+ return VERSION.SDK_INT >= VERSION_CODES.N && enabledFlag;
+ }
+
+ /**
+ * Determines if a call waiting tone should be played for the the given call state (see {@link
+ * State}).
+ *
+ * @param callState the call state for the call being checked.
+ * @return {@code true} if the call waiting tone should be played, {@code false} otherwise.
+ */
+ public boolean shouldPlayCallWaitingTone(int callState) {
+ return isDialerRingingEnabled()
+ && translateCallStateForCallWaiting(callState) == State.CALL_WAITING
+ && !mInCallTonePlayer.isPlayingTone();
+ }
+
+ /** Plays the call waiting tone. */
+ public void playCallWaitingTone() {
+ if (!isDialerRingingEnabled()) {
+ return;
+ }
+ mInCallTonePlayer.play(InCallTonePlayer.TONE_CALL_WAITING);
+ }
+
+ /** Stops playing the call waiting tone. */
+ public void stopCallWaitingTone() {
+ if (!isDialerRingingEnabled()) {
+ return;
+ }
+ mInCallTonePlayer.stop();
+ }
+
+ void setDialerRingingEnabledForTesting(boolean status) {
+ mIsDialerRingingEnabledForTesting = status;
+ }
+}
diff --git a/java/com/android/incallui/ringtone/InCallTonePlayer.java b/java/com/android/incallui/ringtone/InCallTonePlayer.java
new file mode 100644
index 000000000..c76b41d72
--- /dev/null
+++ b/java/com/android/incallui/ringtone/InCallTonePlayer.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.support.annotation.NonNull;
+import android.support.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 mToneGeneratorFactory;
+ @NonNull private final PausableExecutor mExecutor;
+ private @Nullable CountDownLatch mNumPlayingTones;
+
+ /**
+ * 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) {
+ mToneGeneratorFactory = Objects.requireNonNull(toneGeneratorFactory);
+ mExecutor = Objects.requireNonNull(executor);
+ }
+
+ /** @return {@code true} if a tone is currently playing, {@code false} otherwise. */
+ public boolean isPlayingTone() {
+ return mNumPlayingTones != null && mNumPlayingTones.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);
+ mNumPlayingTones = new CountDownLatch(1);
+ mExecutor.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 = mToneGeneratorFactory.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.
+ */
+ mExecutor.milestone();
+ if (mNumPlayingTones != null) {
+ mNumPlayingTones.await(info.toneLengthMillis, TimeUnit.MILLISECONDS);
+ // Allows for synchronization to the point where the tone has completed playing.
+ mExecutor.milestone();
+ }
+ } catch (InterruptedException e) {
+ Log.w(this, "Interrupted while playing in-call tone.");
+ } finally {
+ if (toneGenerator != null) {
+ toneGenerator.release();
+ }
+ if (mNumPlayingTones != null) {
+ mNumPlayingTones.countDown();
+ }
+ // Allows for synchronization to the point where this background thread has cleaned up.
+ mExecutor.milestone();
+ }
+ }
+
+ /** Stops playback of the current tone. */
+ public void stop() {
+ if (mNumPlayingTones != null) {
+ mNumPlayingTones.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
+ + '}';
+ }
+ }
+}
diff --git a/java/com/android/incallui/ringtone/ToneGeneratorFactory.java b/java/com/android/incallui/ringtone/ToneGeneratorFactory.java
new file mode 100644
index 000000000..cd7b11aa9
--- /dev/null
+++ b/java/com/android/incallui/ringtone/ToneGeneratorFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ToneGenerator;
+
+/** Factory used to create {@link ToneGenerator}s. */
+public class ToneGeneratorFactory {
+
+ /**
+ * Creates a new {@link ToneGenerator} to use while in a call.
+ *
+ * @param stream the stream through which to play tones.
+ * @param volume the volume at which to play tones.
+ * @return a new ToneGenerator.
+ */
+ public ToneGenerator newInCallToneGenerator(int stream, int volume) {
+ return new ToneGenerator(stream, volume);
+ }
+}