| /* |
| * 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); |
| } |
| } |