blob: 635d5001c8d9dfb9f939a3d87096f0a294e9ebac [file] [log] [blame]
/*
* Copyright (C) 2015 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.server.telecom;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.telecom.Log;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Vibrator;
import com.android.internal.annotations.VisibleForTesting;
/**
* Controls the ringtone player.
*/
@VisibleForTesting
public class Ringer {
private static final long[] VIBRATION_PATTERN = new long[] {
0, // No delay before starting
1000, // How long to vibrate
1000, // How long to wait before vibrating again
};
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build();
/** Indicate that we want the pattern to repeat at the step which turns on vibration. */
private static final int VIBRATION_PATTERN_REPEAT = 1;
/**
* Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
* calls and explicit ordering is useful for maintaining the proper state of the ringer.
*/
private final SystemSettingsUtil mSystemSettingsUtil;
private final InCallTonePlayer.Factory mPlayerFactory;
private final AsyncRingtonePlayer mRingtonePlayer;
private final Context mContext;
private final Vibrator mVibrator;
private final InCallController mInCallController;
private InCallTonePlayer mCallWaitingPlayer;
private RingtoneFactory mRingtoneFactory;
/**
* Call objects that are ringing or call-waiting. These are used only for logging purposes.
*/
private Call mRingingCall;
private Call mCallWaitingCall;
/**
* Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
*/
private boolean mIsVibrating = false;
/** Initializes the Ringer. */
@VisibleForTesting
public Ringer(
InCallTonePlayer.Factory playerFactory,
Context context,
SystemSettingsUtil systemSettingsUtil,
AsyncRingtonePlayer asyncRingtonePlayer,
RingtoneFactory ringtoneFactory,
Vibrator vibrator,
InCallController inCallController) {
mSystemSettingsUtil = systemSettingsUtil;
mPlayerFactory = playerFactory;
mContext = context;
// We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
// vibrator object will be isolated from others.
mVibrator = vibrator;
mRingtonePlayer = asyncRingtonePlayer;
mRingtoneFactory = ringtoneFactory;
mInCallController = inCallController;
}
public boolean startRinging(Call foregroundCall) {
AudioManager audioManager =
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
boolean isRingerAudible = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
return false;
}
if (foregroundCall == null) {
Log.wtf(this, "startRinging called with null foreground call.");
return false;
}
if (mInCallController.doesConnectedDialerSupportRinging()) {
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
return isRingerAudible;
}
stopCallWaiting();
if (!shouldRingForContact(foregroundCall.getContactUri())) {
return false;
}
// Don't ring/acquire focus if there is no ringtone
if (mRingtoneFactory.getRingtone(foregroundCall) == null) {
isRingerAudible = false;
}
if (isRingerAudible) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
// Because we wait until a contact info query to complete before processing a
// call (for the purposes of direct-to-voicemail), the information about custom
// ringtones should be available by the time this code executes. We can safely
// request the custom ringtone from the call and expect it to be current.
mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
} else {
Log.i(this, "startRingingOrCallWaiting, skipping because volume is 0");
}
if (shouldVibrate(mContext) && !mIsVibrating) {
mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
VIBRATION_ATTRIBUTES);
mIsVibrating = true;
}
return isRingerAudible;
}
public void startCallWaiting(Call call) {
if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
return;
}
if (mInCallController.doesConnectedDialerSupportRinging()) {
Log.addEvent(call, LogUtils.Events.SKIP_RINGING);
return;
}
Log.v(this, "Playing call-waiting tone.");
stopRinging();
if (mCallWaitingPlayer == null) {
Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE);
mCallWaitingCall = call;
mCallWaitingPlayer =
mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
mCallWaitingPlayer.startTone();
}
}
public void stopRinging() {
if (mRingingCall != null) {
Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
mRingingCall = null;
}
mRingtonePlayer.stop();
if (mIsVibrating) {
mVibrator.cancel();
mIsVibrating = false;
}
}
public void stopCallWaiting() {
Log.v(this, "stop call waiting.");
if (mCallWaitingPlayer != null) {
if (mCallWaitingCall != null) {
Log.addEvent(mCallWaitingCall, LogUtils.Events.STOP_CALL_WAITING_TONE);
mCallWaitingCall = null;
}
mCallWaitingPlayer.stopTone();
mCallWaitingPlayer = null;
}
}
private boolean shouldRingForContact(Uri contactUri) {
final NotificationManager manager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
final Bundle extras = new Bundle();
if (contactUri != null) {
extras.putStringArray(Notification.EXTRA_PEOPLE, new String[] {contactUri.toString()});
}
return manager.matchesCallFilter(extras);
}
private boolean shouldVibrate(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int ringerMode = audioManager.getRingerModeInternal();
if (getVibrateWhenRinging(context)) {
return ringerMode != AudioManager.RINGER_MODE_SILENT;
} else {
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
}
}
private boolean getVibrateWhenRinging(Context context) {
if (!mVibrator.hasVibrator()) {
return false;
}
return mSystemSettingsUtil.canVibrateWhenRinging(context);
}
}