diff options
Diffstat (limited to 'java/com/android/incallui/ringtone')
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); + } +} |