1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.content.Context;
22import android.media.AudioAttributes;
23import android.media.AudioManager;
24import android.net.Uri;
25import android.os.Bundle;
26import android.os.SystemVibrator;
27import android.os.Vibrator;
28import android.provider.Settings;
29import android.telecom.CallState;
30
31import java.util.LinkedList;
32import java.util.List;
33
34/**
35 * Controls the ringtone player.
36 */
37final class Ringer extends CallsManagerListenerBase {
38    private static final long[] VIBRATION_PATTERN = new long[] {
39        0, // No delay before starting
40        1000, // How long to vibrate
41        1000, // How long to wait before vibrating again
42    };
43
44    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
45            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
46            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
47            .build();
48
49    /** Indicate that we want the pattern to repeat at the step which turns on vibration. */
50    private static final int VIBRATION_PATTERN_REPEAT = 1;
51
52    private final AsyncRingtonePlayer mRingtonePlayer;
53
54    /**
55     * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
56     * calls and explicit ordering is useful for maintaining the proper state of the ringer.
57     */
58    private final List<Call> mRingingCalls = new LinkedList<>();
59
60    private final CallAudioManager mCallAudioManager;
61    private final CallsManager mCallsManager;
62    private final InCallTonePlayer.Factory mPlayerFactory;
63    private final Context mContext;
64    private final Vibrator mVibrator;
65
66    private InCallTonePlayer mCallWaitingPlayer;
67
68    /**
69     * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
70     */
71    private boolean mIsVibrating = false;
72
73    /** Initializes the Ringer. */
74    Ringer(
75            CallAudioManager callAudioManager,
76            CallsManager callsManager,
77            InCallTonePlayer.Factory playerFactory,
78            Context context) {
79
80        mCallAudioManager = callAudioManager;
81        mCallsManager = callsManager;
82        mPlayerFactory = playerFactory;
83        mContext = context;
84        // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
85        // vibrator object will be isolated from others.
86        mVibrator = new SystemVibrator(context);
87        mRingtonePlayer = new AsyncRingtonePlayer(context);
88    }
89
90    @Override
91    public void onCallAdded(final Call call) {
92        if (call.isIncoming() && call.getState() == CallState.RINGING) {
93            if (mRingingCalls.contains(call)) {
94                Log.wtf(this, "New ringing call is already in list of unanswered calls");
95            }
96            mRingingCalls.add(call);
97            updateRinging();
98        }
99    }
100
101    @Override
102    public void onCallRemoved(Call call) {
103        removeFromUnansweredCall(call);
104    }
105
106    @Override
107    public void onCallStateChanged(Call call, int oldState, int newState) {
108        if (newState != CallState.RINGING) {
109            removeFromUnansweredCall(call);
110        }
111    }
112
113    @Override
114    public void onIncomingCallAnswered(Call call) {
115        onRespondedToIncomingCall(call);
116    }
117
118    @Override
119    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
120        onRespondedToIncomingCall(call);
121    }
122
123    @Override
124    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
125        if (mRingingCalls.contains(oldForegroundCall) ||
126                mRingingCalls.contains(newForegroundCall)) {
127            updateRinging();
128        }
129    }
130
131    /**
132     * Silences the ringer for any actively ringing calls.
133     */
134    void silence() {
135        // Remove all calls from the "ringing" set and then update the ringer.
136        mRingingCalls.clear();
137        updateRinging();
138    }
139
140    private void onRespondedToIncomingCall(Call call) {
141        // Only stop the ringer if this call is the top-most incoming call.
142        if (getTopMostUnansweredCall() == call) {
143            removeFromUnansweredCall(call);
144        }
145    }
146
147    private Call getTopMostUnansweredCall() {
148        return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
149    }
150
151    /**
152     * Removes the specified call from the list of unanswered incoming calls and updates the ringer
153     * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
154     * present in the list of incoming calls.
155     */
156    private void removeFromUnansweredCall(Call call) {
157        mRingingCalls.remove(call);
158        updateRinging();
159    }
160
161    private void updateRinging() {
162        if (mRingingCalls.isEmpty()) {
163            stopRinging();
164            stopCallWaiting();
165        } else {
166            startRingingOrCallWaiting();
167        }
168    }
169
170    private void startRingingOrCallWaiting() {
171        Call foregroundCall = mCallsManager.getForegroundCall();
172        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
173
174        if (mRingingCalls.contains(foregroundCall)) {
175            // The foreground call is one of incoming calls so play the ringer out loud.
176            stopCallWaiting();
177
178            if (!shouldRingForContact(foregroundCall.getContactUri())) {
179                return;
180            }
181
182            AudioManager audioManager =
183                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
184            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
185                Log.v(this, "startRingingOrCallWaiting");
186                mCallAudioManager.setIsRinging(true);
187
188                // Because we wait until a contact info query to complete before processing a
189                // call (for the purposes of direct-to-voicemail), the information about custom
190                // ringtones should be available by the time this code executes. We can safely
191                // request the custom ringtone from the call and expect it to be current.
192                mRingtonePlayer.play(foregroundCall.getRingtone());
193            } else {
194                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
195            }
196
197            if (shouldVibrate(mContext) && !mIsVibrating) {
198                mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
199                        VIBRATION_ATTRIBUTES);
200                mIsVibrating = true;
201            }
202        } else if (foregroundCall != null) {
203            // The first incoming call added to Telecom is not a foreground call at this point
204            // in time. If the current foreground call is null at point, don't play call-waiting
205            // as the call will eventually be promoted to the foreground call and play the
206            // ring tone.
207            Log.v(this, "Playing call-waiting tone.");
208
209            // All incoming calls are in background so play call waiting.
210            stopRinging();
211
212            if (mCallWaitingPlayer == null) {
213                mCallWaitingPlayer =
214                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
215                mCallWaitingPlayer.startTone();
216            }
217        }
218    }
219
220    private boolean shouldRingForContact(Uri contactUri) {
221        final NotificationManager manager =
222                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
223        final Bundle extras = new Bundle();
224        if (contactUri != null) {
225            extras.putStringArray(Notification.EXTRA_PEOPLE, new String[] {contactUri.toString()});
226        }
227        return manager.matchesCallFilter(extras);
228    }
229
230    private void stopRinging() {
231        Log.v(this, "stopRinging");
232
233        mRingtonePlayer.stop();
234
235        if (mIsVibrating) {
236            mVibrator.cancel();
237            mIsVibrating = false;
238        }
239
240        // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
241        // focus are voluntary so releasing focus too early is not detrimental.
242        mCallAudioManager.setIsRinging(false);
243    }
244
245    private void stopCallWaiting() {
246        Log.v(this, "stop call waiting.");
247        if (mCallWaitingPlayer != null) {
248            mCallWaitingPlayer.stopTone();
249            mCallWaitingPlayer = null;
250        }
251    }
252
253    private boolean shouldVibrate(Context context) {
254        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
255        int ringerMode = audioManager.getRingerMode();
256        if (getVibrateWhenRinging(context)) {
257            return ringerMode != AudioManager.RINGER_MODE_SILENT;
258        } else {
259            return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
260        }
261    }
262
263    private boolean getVibrateWhenRinging(Context context) {
264        if (!mVibrator.hasVibrator()) {
265            return false;
266        }
267        return Settings.System.getInt(context.getContentResolver(),
268                Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
269    }
270}
271