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;
29
30import java.util.LinkedList;
31import java.util.List;
32
33/**
34 * Controls the ringtone player.
35 * TODO: Turn this into a proper state machine: Ringing, CallWaiting, Stopped.
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 int STATE_RINGING = 1;
45    private static final int STATE_CALL_WAITING = 2;
46    private static final int STATE_STOPPED = 3;
47
48    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
49            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
50            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
51            .build();
52
53    /** Indicate that we want the pattern to repeat at the step which turns on vibration. */
54    private static final int VIBRATION_PATTERN_REPEAT = 1;
55
56    private final AsyncRingtonePlayer mRingtonePlayer;
57
58    /**
59     * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
60     * calls and explicit ordering is useful for maintaining the proper state of the ringer.
61     */
62    private final List<Call> mRingingCalls = new LinkedList<>();
63
64    private final CallAudioManager mCallAudioManager;
65    private final CallsManager mCallsManager;
66    private final InCallTonePlayer.Factory mPlayerFactory;
67    private final Context mContext;
68    private final Vibrator mVibrator;
69
70    private int mState = STATE_STOPPED;
71    private InCallTonePlayer mCallWaitingPlayer;
72
73    /**
74     * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
75     */
76    private boolean mIsVibrating = false;
77
78    /** Initializes the Ringer. */
79    Ringer(
80            CallAudioManager callAudioManager,
81            CallsManager callsManager,
82            InCallTonePlayer.Factory playerFactory,
83            Context context) {
84
85        mCallAudioManager = callAudioManager;
86        mCallsManager = callsManager;
87        mPlayerFactory = playerFactory;
88        mContext = context;
89        // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
90        // vibrator object will be isolated from others.
91        mVibrator = new SystemVibrator(context);
92        mRingtonePlayer = new AsyncRingtonePlayer(context);
93    }
94
95    @Override
96    public void onCallAdded(final Call call) {
97        if (call.isIncoming() && call.getState() == CallState.RINGING) {
98            if (mRingingCalls.contains(call)) {
99                Log.wtf(this, "New ringing call is already in list of unanswered calls");
100            }
101            mRingingCalls.add(call);
102            updateRinging(call);
103        }
104    }
105
106    @Override
107    public void onCallRemoved(Call call) {
108        removeFromUnansweredCall(call);
109    }
110
111    @Override
112    public void onCallStateChanged(Call call, int oldState, int newState) {
113        if (newState != CallState.RINGING) {
114            removeFromUnansweredCall(call);
115        }
116    }
117
118    @Override
119    public void onIncomingCallAnswered(Call call) {
120        onRespondedToIncomingCall(call);
121    }
122
123    @Override
124    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
125        onRespondedToIncomingCall(call);
126    }
127
128    @Override
129    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
130        Call ringingCall = null;
131        if (mRingingCalls.contains(newForegroundCall)) {
132            ringingCall = newForegroundCall;
133        } else if (mRingingCalls.contains(oldForegroundCall)) {
134            ringingCall = oldForegroundCall;
135        }
136        if (ringingCall != null) {
137            updateRinging(ringingCall);
138        }
139    }
140
141    /**
142     * Silences the ringer for any actively ringing calls.
143     */
144    void silence() {
145        // Remove all calls from the "ringing" set and then update the ringer.
146        mRingingCalls.clear();
147        updateRinging(null);
148    }
149
150    private void onRespondedToIncomingCall(Call call) {
151        // Only stop the ringer if this call is the top-most incoming call.
152        if (getTopMostUnansweredCall() == call) {
153            removeFromUnansweredCall(call);
154        }
155    }
156
157    private Call getTopMostUnansweredCall() {
158        return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
159    }
160
161    /**
162     * Removes the specified call from the list of unanswered incoming calls and updates the ringer
163     * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
164     * present in the list of incoming calls.
165     */
166    private void removeFromUnansweredCall(Call call) {
167        mRingingCalls.remove(call);
168        updateRinging(call);
169    }
170
171    private void updateRinging(Call call) {
172        if (mRingingCalls.isEmpty()) {
173            stopRinging(call, "No more ringing calls found");
174            stopCallWaiting(call);
175        } else {
176            startRingingOrCallWaiting(call);
177        }
178    }
179
180    private void startRingingOrCallWaiting(Call call) {
181        Call foregroundCall = mCallsManager.getForegroundCall();
182        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
183
184        if (mRingingCalls.contains(foregroundCall)) {
185            // The foreground call is one of incoming calls so play the ringer out loud.
186            stopCallWaiting(call);
187
188            if (!shouldRingForContact(foregroundCall.getContactUri())) {
189                return;
190            }
191
192            AudioManager audioManager =
193                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
194            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
195                if (mState != STATE_RINGING) {
196                    Log.event(call, Log.Events.START_RINGER);
197                    mState = STATE_RINGING;
198                }
199                mCallAudioManager.setIsRinging(call, true);
200
201                // Because we wait until a contact info query to complete before processing a
202                // call (for the purposes of direct-to-voicemail), the information about custom
203                // ringtones should be available by the time this code executes. We can safely
204                // request the custom ringtone from the call and expect it to be current.
205                mRingtonePlayer.play(foregroundCall.getRingtone());
206            } else {
207                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
208            }
209
210            if (shouldVibrate(mContext) && !mIsVibrating) {
211                mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
212                        VIBRATION_ATTRIBUTES);
213                mIsVibrating = true;
214            }
215        } else if (foregroundCall != null) {
216            // The first incoming call added to Telecom is not a foreground call at this point
217            // in time. If the current foreground call is null at point, don't play call-waiting
218            // as the call will eventually be promoted to the foreground call and play the
219            // ring tone.
220            Log.v(this, "Playing call-waiting tone.");
221
222            // All incoming calls are in background so play call waiting.
223            stopRinging(call, "Stop for call-waiting");
224
225
226            if (mState != STATE_CALL_WAITING) {
227                Log.event(call, Log.Events.START_CALL_WAITING_TONE);
228                mState = STATE_CALL_WAITING;
229            }
230
231            if (mCallWaitingPlayer == null) {
232                mCallWaitingPlayer =
233                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
234                mCallWaitingPlayer.startTone();
235            }
236        }
237    }
238
239    private boolean shouldRingForContact(Uri contactUri) {
240        final NotificationManager manager =
241                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
242        final Bundle extras = new Bundle();
243        if (contactUri != null) {
244            extras.putStringArray(Notification.EXTRA_PEOPLE, new String[] {contactUri.toString()});
245        }
246        return manager.matchesCallFilter(extras);
247    }
248
249    private void stopRinging(Call call, String reasonTag) {
250        if (mState == STATE_RINGING) {
251            Log.event(call, Log.Events.STOP_RINGER, reasonTag);
252            mState = STATE_STOPPED;
253        }
254
255        mRingtonePlayer.stop();
256
257        if (mIsVibrating) {
258            mVibrator.cancel();
259            mIsVibrating = false;
260        }
261
262        // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
263        // focus are voluntary so releasing focus too early is not detrimental.
264        mCallAudioManager.setIsRinging(call, false);
265    }
266
267    private void stopCallWaiting(Call call) {
268        Log.v(this, "stop call waiting.");
269        if (mCallWaitingPlayer != null) {
270            mCallWaitingPlayer.stopTone();
271            mCallWaitingPlayer = null;
272        }
273
274        if (mState == STATE_CALL_WAITING) {
275            Log.event(call, Log.Events.STOP_CALL_WAITING_TONE);
276            mState = STATE_STOPPED;
277        }
278    }
279
280    private boolean shouldVibrate(Context context) {
281        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
282        int ringerMode = audioManager.getRingerModeInternal();
283        if (getVibrateWhenRinging(context)) {
284            return ringerMode != AudioManager.RINGER_MODE_SILENT;
285        } else {
286            return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
287        }
288    }
289
290    private boolean getVibrateWhenRinging(Context context) {
291        if (!mVibrator.hasVibrator()) {
292            return false;
293        }
294        return Settings.System.getInt(context.getContentResolver(),
295                Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
296    }
297}
298