blob: 0edf62ff5c862cde149d521424767045fa5bad85 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2006 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.phone;
18
19import com.android.internal.telephony.Call;
20import com.android.internal.telephony.CallManager;
21import com.android.internal.telephony.CallerInfo;
22import com.android.internal.telephony.CallerInfoAsyncQuery;
23import com.android.internal.telephony.Connection;
24import com.android.internal.telephony.Phone;
25import com.android.internal.telephony.PhoneConstants;
26import com.android.internal.telephony.PhoneBase;
27import com.android.internal.telephony.TelephonyCapabilities;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070028import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
29import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
30import com.android.internal.telephony.cdma.SignalToneUtil;
31
32import android.app.ActivityManagerNative;
33import android.bluetooth.BluetoothAdapter;
34import android.bluetooth.BluetoothHeadset;
35import android.bluetooth.BluetoothProfile;
36import android.content.Context;
John Spurlock6ee06d02014-07-18 20:06:20 -040037import android.media.AudioAttributes;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070038import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.AsyncResult;
42import android.os.Handler;
43import android.os.Message;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070044import android.os.SystemProperties;
45import android.os.SystemVibrator;
46import android.os.Vibrator;
47import android.provider.CallLog.Calls;
48import android.provider.Settings;
Anders Kristensen0b35f042014-02-27 14:31:07 -080049import android.telephony.DisconnectCause;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070050import android.telephony.PhoneNumberUtils;
51import android.telephony.PhoneStateListener;
52import android.telephony.TelephonyManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070053import android.util.EventLog;
54import android.util.Log;
55
56/**
57 * Phone app module that listens for phone state changes and various other
58 * events from the telephony layer, and triggers any resulting UI behavior
59 * (like starting the Ringer and Incoming Call UI, playing in-call tones,
60 * updating notifications, writing call log entries, etc.)
61 */
62public class CallNotifier extends Handler
63 implements CallerInfoAsyncQuery.OnQueryCompleteListener {
64 private static final String LOG_TAG = "CallNotifier";
65 private static final boolean DBG =
66 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
67 private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
68
69 // Maximum time we allow the CallerInfo query to run,
70 // before giving up and falling back to the default ringtone.
71 private static final int RINGTONE_QUERY_WAIT_TIME = 500; // msec
72
Santos Cordon7d4ddf62013-07-10 11:58:08 -070073 // Time to display the DisplayInfo Record sent by CDMA network
74 private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
75
John Spurlock6ee06d02014-07-18 20:06:20 -040076 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
77 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
78 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
79 .build();
80
Santos Cordon7d4ddf62013-07-10 11:58:08 -070081 /** The singleton instance. */
82 private static CallNotifier sInstance;
83
Santos Cordon7d4ddf62013-07-10 11:58:08 -070084 // values used to track the query state
85 private static final int CALLERINFO_QUERY_READY = 0;
86 private static final int CALLERINFO_QUERYING = -1;
87
88 // the state of the CallerInfo Query.
89 private int mCallerInfoQueryState;
90
91 // object used to synchronize access to mCallerInfoQueryState
92 private Object mCallerInfoQueryStateGuard = new Object();
93
94 // Event used to indicate a query timeout.
95 private static final int RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT = 100;
96
97 // Events generated internally:
98 private static final int PHONE_MWI_CHANGED = 21;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070099 private static final int DISPLAYINFO_NOTIFICATION_DONE = 24;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700100 private static final int UPDATE_IN_CALL_NOTIFICATION = 27;
101
102 // Emergency call related defines:
103 private static final int EMERGENCY_TONE_OFF = 0;
104 private static final int EMERGENCY_TONE_ALERT = 1;
105 private static final int EMERGENCY_TONE_VIBRATE = 2;
106
107 private PhoneGlobals mApplication;
108 private CallManager mCM;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700109 private Ringer mRinger;
110 private BluetoothHeadset mBluetoothHeadset;
111 private CallLogger mCallLogger;
112 private boolean mSilentRingerRequested;
113
114 // ToneGenerator instance for playing SignalInfo tones
115 private ToneGenerator mSignalInfoToneGenerator;
116
117 // The tone volume relative to other sounds in the stream SignalInfo
118 private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80;
119
120 private Call.State mPreviousCdmaCallState;
121 private boolean mVoicePrivacyState = false;
122 private boolean mIsCdmaRedialCall = false;
123
124 // Emergency call tone and vibrate:
125 private int mIsEmergencyToneOn;
126 private int mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
127 private EmergencyTonePlayerVibrator mEmergencyTonePlayerVibrator;
128
129 // Ringback tone player
130 private InCallTonePlayer mInCallRingbackTonePlayer;
131
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700132 // Cached AudioManager
133 private AudioManager mAudioManager;
134
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700135 private final BluetoothManager mBluetoothManager;
136
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700137 /**
138 * Initialize the singleton CallNotifier instance.
139 * This is only done once, at startup, from PhoneApp.onCreate().
140 */
141 /* package */ static CallNotifier init(PhoneGlobals app, Phone phone, Ringer ringer,
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700142 CallLogger callLogger, CallStateMonitor callStateMonitor,
Sailesh Nepal23d9ed72014-07-03 09:40:26 -0700143 BluetoothManager bluetoothManager) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700144 synchronized (CallNotifier.class) {
145 if (sInstance == null) {
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700146 sInstance = new CallNotifier(app, phone, ringer, callLogger, callStateMonitor,
Sailesh Nepal23d9ed72014-07-03 09:40:26 -0700147 bluetoothManager);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700148 } else {
149 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
150 }
151 return sInstance;
152 }
153 }
154
155 /** Private constructor; @see init() */
156 private CallNotifier(PhoneGlobals app, Phone phone, Ringer ringer, CallLogger callLogger,
Sailesh Nepal23d9ed72014-07-03 09:40:26 -0700157 CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700158 mApplication = app;
159 mCM = app.mCM;
160 mCallLogger = callLogger;
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700161 mBluetoothManager = bluetoothManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700162
163 mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
164
165 callStateMonitor.addListener(this);
166
167 createSignalInfoToneGenerator();
168
169 mRinger = ringer;
170 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
171 if (adapter != null) {
172 adapter.getProfileProxy(mApplication.getApplicationContext(),
173 mBluetoothProfileServiceListener,
174 BluetoothProfile.HEADSET);
175 }
176
177 TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
178 Context.TELEPHONY_SERVICE);
179 telephonyManager.listen(mPhoneStateListener,
180 PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
181 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
182 }
183
184 private void createSignalInfoToneGenerator() {
185 // Instantiate the ToneGenerator for SignalInfo and CallWaiting
186 // TODO: We probably don't need the mSignalInfoToneGenerator instance
187 // around forever. Need to change it so as to create a ToneGenerator instance only
188 // when a tone is being played and releases it after its done playing.
189 if (mSignalInfoToneGenerator == null) {
190 try {
191 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
192 TONE_RELATIVE_VOLUME_SIGNALINFO);
193 Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay");
194 } catch (RuntimeException e) {
195 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
196 "mSignalInfoToneGenerator: " + e);
197 mSignalInfoToneGenerator = null;
198 }
199 } else {
200 Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping");
201 }
202 }
203
204 @Override
205 public void handleMessage(Message msg) {
206 switch (msg.what) {
207 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
208 log("RINGING... (new)");
209 onNewRingingConnection((AsyncResult) msg.obj);
210 mSilentRingerRequested = false;
211 break;
212
213 case CallStateMonitor.PHONE_INCOMING_RING:
214 // repeat the ring when requested by the RIL, and when the user has NOT
215 // specifically requested silence.
216 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
217 PhoneBase pb = (PhoneBase)((AsyncResult)msg.obj).result;
218
219 if ((pb.getState() == PhoneConstants.State.RINGING)
220 && (mSilentRingerRequested == false)) {
221 if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
222 mRinger.ring();
223 } else {
224 if (DBG) log("RING before NEW_RING, skipping");
225 }
226 }
227 break;
228
229 case CallStateMonitor.PHONE_STATE_CHANGED:
230 onPhoneStateChanged((AsyncResult) msg.obj);
231 break;
232
233 case CallStateMonitor.PHONE_DISCONNECT:
234 if (DBG) log("DISCONNECT");
235 onDisconnect((AsyncResult) msg.obj);
236 break;
237
238 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
239 onUnknownConnectionAppeared((AsyncResult) msg.obj);
240 break;
241
242 case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700243 onCustomRingtoneQueryTimeout((Connection) msg.obj);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700244 break;
245
246 case PHONE_MWI_CHANGED:
247 onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
248 break;
249
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700250 case CallStateMonitor.PHONE_STATE_DISPLAYINFO:
251 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
252 onDisplayInfo((AsyncResult) msg.obj);
253 break;
254
255 case CallStateMonitor.PHONE_STATE_SIGNALINFO:
256 if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
257 onSignalInfo((AsyncResult) msg.obj);
258 break;
259
260 case DISPLAYINFO_NOTIFICATION_DONE:
261 if (DBG) log("Received Display Info notification done event ...");
262 CdmaDisplayInfo.dismissDisplayInfoRecord();
263 break;
264
265 case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE:
266 if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
267 mApplication.handleOtaspEvent(msg);
268 break;
269
270 case CallStateMonitor.PHONE_ENHANCED_VP_ON:
271 if (DBG) log("PHONE_ENHANCED_VP_ON...");
272 if (!mVoicePrivacyState) {
273 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
274 new InCallTonePlayer(toneToPlay).start();
275 mVoicePrivacyState = true;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700276 }
277 break;
278
279 case CallStateMonitor.PHONE_ENHANCED_VP_OFF:
280 if (DBG) log("PHONE_ENHANCED_VP_OFF...");
281 if (mVoicePrivacyState) {
282 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
283 new InCallTonePlayer(toneToPlay).start();
284 mVoicePrivacyState = false;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700285 }
286 break;
287
288 case CallStateMonitor.PHONE_RINGBACK_TONE:
Ihab Awad277011f2014-05-28 16:51:33 -0700289 // DISABLED. The Telecomm and new ConnectionService layers are now responsible.
290 // onRingbackTone((AsyncResult) msg.obj);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700291 break;
292
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700293 default:
294 // super.handleMessage(msg);
295 }
296 }
297
298 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
299 @Override
300 public void onMessageWaitingIndicatorChanged(boolean mwi) {
301 onMwiChanged(mwi);
302 }
303
304 @Override
305 public void onCallForwardingIndicatorChanged(boolean cfi) {
306 onCfiChanged(cfi);
307 }
308 };
309
310 /**
311 * Handles a "new ringing connection" event from the telephony layer.
312 */
313 private void onNewRingingConnection(AsyncResult r) {
314 Connection c = (Connection) r.result;
315 log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
316 Call ringing = c.getCall();
317 Phone phone = ringing.getPhone();
318
319 // Check for a few cases where we totally ignore incoming calls.
320 if (ignoreAllIncomingCalls(phone)) {
321 // Immediately reject the call, without even indicating to the user
322 // that an incoming call occurred. (This will generally send the
323 // caller straight to voicemail, just as if we *had* shown the
324 // incoming-call UI and the user had declined the call.)
325 PhoneUtils.hangupRingingCall(ringing);
326 return;
327 }
328
329 if (!c.isRinging()) {
330 Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
331 // This is a very strange case: an incoming call that stopped
332 // ringing almost instantly after the onNewRingingConnection()
333 // event. There's nothing we can do here, so just bail out
334 // without doing anything. (But presumably we'll log it in
335 // the call log when the disconnect event comes in...)
336 return;
337 }
338
339 // Stop any signalInfo tone being played on receiving a Call
340 stopSignalInfoTone();
341
342 Call.State state = c.getState();
343 // State will be either INCOMING or WAITING.
344 if (VDBG) log("- connection is ringing! state = " + state);
345 // if (DBG) PhoneUtils.dumpCallState(mPhone);
346
347 // No need to do any service state checks here (like for
348 // "emergency mode"), since in those states the SIM won't let
349 // us get incoming connections in the first place.
350
351 // TODO: Consider sending out a serialized broadcast Intent here
352 // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
353 // ringer and going to the in-call UI. The intent should contain
354 // the caller-id info for the current connection, and say whether
355 // it would be a "call waiting" call or a regular ringing call.
356 // If anybody consumed the broadcast, we'd bail out without
357 // ringing or bringing up the in-call UI.
358 //
359 // This would give 3rd party apps a chance to listen for (and
360 // intercept) new ringing connections. An app could reject the
361 // incoming call by consuming the broadcast and doing nothing, or
362 // it could "pick up" the call (without any action by the user!)
363 // via some future TelephonyManager API.
364 //
365 // See bug 1312336 for more details.
366 // We'd need to protect this with a new "intercept incoming calls"
367 // system permission.
368
369 // Obtain a partial wake lock to make sure the CPU doesn't go to
370 // sleep before we finish bringing up the InCallScreen.
371 // (This will be upgraded soon to a full wake lock; see
372 // showIncomingCall().)
373 if (VDBG) log("Holding wake lock on new incoming connection.");
374 mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL);
375
376 // - don't ring for call waiting connections
377 // - do this before showing the incoming call panel
Christine Chenb5e4b652013-09-19 11:20:18 -0700378 startIncomingCallQuery(c);
Chiao Cheng312b9c92013-09-16 15:40:53 -0700379
Christine Chenb5e4b652013-09-19 11:20:18 -0700380
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700381
382 // Note we *don't* post a status bar notification here, since
383 // we're not necessarily ready to actually show the incoming call
384 // to the user. (For calls in the INCOMING state, at least, we
385 // still need to run a caller-id query, and we may not even ring
386 // at all if the "send directly to voicemail" flag is set.)
387 //
388 // Instead, we update the notification (and potentially launch the
389 // InCallScreen) from the showIncomingCall() method, which runs
390 // when the caller-id query completes or times out.
391
392 if (VDBG) log("- onNewRingingConnection() done.");
393 }
394
395 /**
396 * Determines whether or not we're allowed to present incoming calls to the
397 * user, based on the capabilities and/or current state of the device.
398 *
399 * If this method returns true, that means we should immediately reject the
400 * current incoming call, without even indicating to the user that an
401 * incoming call occurred.
402 *
403 * (We only reject incoming calls in a few cases, like during an OTASP call
404 * when we can't interrupt the user, or if the device hasn't completed the
405 * SetupWizard yet. We also don't allow incoming calls on non-voice-capable
406 * devices. But note that we *always* allow incoming calls while in ECM.)
407 *
408 * @return true if we're *not* allowed to present an incoming call to
409 * the user.
410 */
411 private boolean ignoreAllIncomingCalls(Phone phone) {
412 // Incoming calls are totally ignored on non-voice-capable devices.
413 if (!PhoneGlobals.sVoiceCapable) {
414 // ...but still log a warning, since we shouldn't have gotten this
415 // event in the first place! (Incoming calls *should* be blocked at
416 // the telephony layer on non-voice-capable capable devices.)
417 Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring...");
418 return true;
419 }
420
421 // In ECM (emergency callback mode), we ALWAYS allow incoming calls
422 // to get through to the user. (Note that ECM is applicable only to
423 // voice-capable CDMA devices).
424 if (PhoneUtils.isPhoneInEcm(phone)) {
425 if (DBG) log("Incoming call while in ECM: always allow...");
426 return false;
427 }
428
429 // Incoming calls are totally ignored if the device isn't provisioned yet.
430 boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(),
431 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
432 if (!provisioned) {
433 Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
434 return true;
435 }
436
437 // Incoming calls are totally ignored if an OTASP call is active.
438 if (TelephonyCapabilities.supportsOtasp(phone)) {
439 boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState
440 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
441 boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState
442 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG);
443 boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState;
444
445 if (spcState) {
446 Log.i(LOG_TAG, "Ignoring incoming call: OTA call is active");
447 return true;
448 } else if (activateState || dialogState) {
449 // We *are* allowed to receive incoming calls at this point.
450 // But clear out any residual OTASP UI first.
451 // TODO: It's an MVC violation to twiddle the OTA UI state here;
452 // we should instead provide a higher-level API via OtaUtils.
453 if (dialogState) mApplication.dismissOtaDialogs();
454 mApplication.clearOtaState();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700455 return false;
456 }
457 }
458
459 // Normal case: allow this call to be presented to the user.
460 return false;
461 }
462
463 /**
464 * Helper method to manage the start of incoming call queries
465 */
466 private void startIncomingCallQuery(Connection c) {
467 // TODO: cache the custom ringer object so that subsequent
468 // calls will not need to do this query work. We can keep
469 // the MRU ringtones in memory. We'll still need to hit
470 // the database to get the callerinfo to act as a key,
471 // but at least we can save the time required for the
472 // Media player setup. The only issue with this is that
473 // we may need to keep an eye on the resources the Media
474 // player uses to keep these ringtones around.
475
476 // make sure we're in a state where we can be ready to
477 // query a ringtone uri.
478 boolean shouldStartQuery = false;
479 synchronized (mCallerInfoQueryStateGuard) {
480 if (mCallerInfoQueryState == CALLERINFO_QUERY_READY) {
481 mCallerInfoQueryState = CALLERINFO_QUERYING;
482 shouldStartQuery = true;
483 }
484 }
485 if (shouldStartQuery) {
486 // Reset the ringtone to the default first.
487 mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
488
489 // query the callerinfo to try to get the ringer.
490 PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700491 mApplication, c, this, c);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700492
493 // if this has already been queried then just ring, otherwise
494 // we wait for the alloted time before ringing.
495 if (cit.isFinal) {
496 if (VDBG) log("- CallerInfo already up to date, using available data");
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700497 onQueryComplete(0, c, cit.currentInfo);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700498 } else {
499 if (VDBG) log("- Starting query, posting timeout message.");
500
501 // Phone number (via getAddress()) is stored in the message to remember which
502 // number is actually used for the look up.
503 sendMessageDelayed(
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700504 Message.obtain(this, RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT, c),
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700505 RINGTONE_QUERY_WAIT_TIME);
506 }
507 // The call to showIncomingCall() will happen after the
508 // queries are complete (or time out).
509 } else {
510 // This should never happen; its the case where an incoming call
511 // arrives at the same time that the query is still being run,
512 // and before the timeout window has closed.
513 EventLog.writeEvent(EventLogTags.PHONE_UI_MULTIPLE_QUERY);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700514 }
515 }
516
517 /**
518 * Performs the final steps of the onNewRingingConnection sequence:
519 * starts the ringer, and brings up the "incoming call" UI.
520 *
521 * Normally, this is called when the CallerInfo query completes (see
522 * onQueryComplete()). In this case, onQueryComplete() has already
523 * configured the Ringer object to use the custom ringtone (if there
524 * is one) for this caller. So we just tell the Ringer to start, and
525 * proceed to the InCallScreen.
526 *
527 * But this method can *also* be called if the
528 * RINGTONE_QUERY_WAIT_TIME timeout expires, which means that the
529 * CallerInfo query is taking too long. In that case, we log a
530 * warning but otherwise we behave the same as in the normal case.
531 * (We still tell the Ringer to start, but it's going to use the
532 * default ringtone.)
533 */
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700534 private void onCustomRingQueryComplete(Connection c) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700535 boolean isQueryExecutionTimeExpired = false;
536 synchronized (mCallerInfoQueryStateGuard) {
537 if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
538 mCallerInfoQueryState = CALLERINFO_QUERY_READY;
539 isQueryExecutionTimeExpired = true;
540 }
541 }
542 if (isQueryExecutionTimeExpired) {
543 // There may be a problem with the query here, since the
544 // default ringtone is playing instead of the custom one.
545 Log.w(LOG_TAG, "CallerInfo query took too long; falling back to default ringtone");
546 EventLog.writeEvent(EventLogTags.PHONE_UI_RINGER_QUERY_ELAPSED);
547 }
548
549 // Make sure we still have an incoming call!
550 //
551 // (It's possible for the incoming call to have been disconnected
552 // while we were running the query. In that case we better not
553 // start the ringer here, since there won't be any future
554 // DISCONNECT event to stop it!)
555 //
556 // Note we don't have to worry about the incoming call going away
557 // *after* this check but before we call mRinger.ring() below,
558 // since in that case we *will* still get a DISCONNECT message sent
559 // to our handler. (And we will correctly stop the ringer when we
560 // process that event.)
561 if (mCM.getState() != PhoneConstants.State.RINGING) {
562 Log.i(LOG_TAG, "onCustomRingQueryComplete: No incoming call! Bailing out...");
563 // Don't start the ringer *or* bring up the "incoming call" UI.
564 // Just bail out.
565 return;
566 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700567 }
568
569 private void onUnknownConnectionAppeared(AsyncResult r) {
570 PhoneConstants.State state = mCM.getState();
571
572 if (state == PhoneConstants.State.OFFHOOK) {
Santos Cordon54fdb592013-09-19 05:16:18 -0700573 if (DBG) log("unknown connection appeared...");
Chiao Cheng312b9c92013-09-16 15:40:53 -0700574
Santos Cordon54fdb592013-09-19 05:16:18 -0700575 onPhoneStateChanged(r);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700576 }
577 }
578
Christine Chenb5e4b652013-09-19 11:20:18 -0700579 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700580 * Updates the phone UI in response to phone state changes.
581 *
582 * Watch out: certain state changes are actually handled by their own
583 * specific methods:
584 * - see onNewRingingConnection() for new incoming calls
585 * - see onDisconnect() for calls being hung up or disconnected
586 */
587 private void onPhoneStateChanged(AsyncResult r) {
588 PhoneConstants.State state = mCM.getState();
589 if (VDBG) log("onPhoneStateChanged: state = " + state);
590
591 // Turn status bar notifications on or off depending upon the state
592 // of the phone. Notification Alerts (audible or vibrating) should
593 // be on if and only if the phone is IDLE.
594 mApplication.notificationMgr.statusBarHelper
595 .enableNotificationAlerts(state == PhoneConstants.State.IDLE);
596
597 Phone fgPhone = mCM.getFgPhone();
598 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
599 if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE)
600 && ((mPreviousCdmaCallState == Call.State.DIALING)
601 || (mPreviousCdmaCallState == Call.State.ALERTING))) {
602 if (mIsCdmaRedialCall) {
603 int toneToPlay = InCallTonePlayer.TONE_REDIAL;
604 new InCallTonePlayer(toneToPlay).start();
605 }
606 // Stop any signal info tone when call moves to ACTIVE state
607 stopSignalInfoTone();
608 }
609 mPreviousCdmaCallState = fgPhone.getForegroundCall().getState();
610 }
611
612 // Have the PhoneApp recompute its mShowBluetoothIndication
613 // flag based on the (new) telephony state.
614 // There's no need to force a UI update since we update the
615 // in-call notification ourselves (below), and the InCallScreen
616 // listens for phone state changes itself.
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700617 mBluetoothManager.updateBluetoothIndication();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700618
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700619 // Update the phone state and other sensor/lock.
620 mApplication.updatePhoneState(state);
621
622 if (state == PhoneConstants.State.OFFHOOK) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700623
624 if (VDBG) log("onPhoneStateChanged: OFF HOOK");
625 // make sure audio is in in-call mode now
626 PhoneUtils.setAudioMode(mCM);
627
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700628 // Since we're now in-call, the Ringer should definitely *not*
629 // be ringing any more. (This is just a sanity-check; we
630 // already stopped the ringer explicitly back in
631 // PhoneUtils.answerCall(), before the call to phone.acceptCall().)
632 // TODO: Confirm that this call really *is* unnecessary, and if so,
633 // remove it!
634 if (DBG) log("stopRing()... (OFFHOOK state)");
635 mRinger.stopRing();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700636 }
637
638 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
639 Connection c = fgPhone.getForegroundCall().getLatestConnection();
Yorke Lee36bb2542014-06-05 08:09:52 -0700640 if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(mApplication,
641 c.getAddress()))) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700642 if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
643 Call.State callState = fgPhone.getForegroundCall().getState();
644 if (mEmergencyTonePlayerVibrator == null) {
645 mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
646 }
647
648 if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
649 mIsEmergencyToneOn = Settings.Global.getInt(
650 mApplication.getContentResolver(),
651 Settings.Global.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
652 if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
653 mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
654 if (mEmergencyTonePlayerVibrator != null) {
655 mEmergencyTonePlayerVibrator.start();
656 }
657 }
658 } else if (callState == Call.State.ACTIVE) {
659 if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) {
660 if (mEmergencyTonePlayerVibrator != null) {
661 mEmergencyTonePlayerVibrator.stop();
662 }
663 }
664 }
665 }
666 }
667
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800668 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM
669 || fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP
Etan Cohen0ca1c802014-07-07 15:35:48 -0700670 || fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800671 || fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_THIRD_PARTY) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700672 Call.State callState = mCM.getActiveFgCallState();
673 if (!callState.isDialing()) {
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800674 // If call gets activated or disconnected before the ringback
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700675 // tone stops, we have to stop it to prevent disturbing.
676 if (mInCallRingbackTonePlayer != null) {
677 mInCallRingbackTonePlayer.stopTone();
678 mInCallRingbackTonePlayer = null;
679 }
680 }
681 }
682 }
683
684 void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
685 if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
686
687 // Clear ringback tone player
688 mInCallRingbackTonePlayer = null;
689
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700690 // Instantiate mSignalInfoToneGenerator
691 createSignalInfoToneGenerator();
692 }
693
694 /**
695 * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
696 * refreshes the CallCard data when it called. If called with this
697 * class itself, it is assumed that we have been waiting for the ringtone
698 * and direct to voicemail settings to update.
699 */
700 @Override
701 public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
702 if (cookie instanceof Long) {
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700703 } else if (cookie instanceof Connection) {
704 final Connection c = (Connection) cookie;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700705 if (VDBG) log("CallerInfo query complete (for CallNotifier), "
706 + "updating state for incoming call..");
707
708 // get rid of the timeout messages
709 removeMessages(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT);
710
711 boolean isQueryExecutionTimeOK = false;
712 synchronized (mCallerInfoQueryStateGuard) {
713 if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
714 mCallerInfoQueryState = CALLERINFO_QUERY_READY;
715 isQueryExecutionTimeOK = true;
716 }
717 }
718 //if we're in the right state
719 if (isQueryExecutionTimeOK) {
720
721 // send directly to voicemail.
722 if (ci.shouldSendToVoicemail) {
723 if (DBG) log("send to voicemail flag detected. hanging up.");
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700724 final Call ringingCall = mCM.getFirstActiveRingingCall();
725 if (ringingCall != null && ringingCall.getLatestConnection() == c) {
726 PhoneUtils.hangupRingingCall(ringingCall);
727 return;
728 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700729 }
730
731 // set the ringtone uri to prepare for the ring.
732 if (ci.contactRingtoneUri != null) {
733 if (DBG) log("custom ringtone found, setting up ringer.");
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700734 Ringer r = mRinger;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700735 r.setCustomRingtoneUri(ci.contactRingtoneUri);
736 }
737 // ring, and other post-ring actions.
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700738 onCustomRingQueryComplete(c);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700739 }
740 }
741 }
742
743 /**
744 * Called when asynchronous CallerInfo query is taking too long (more than
745 * {@link #RINGTONE_QUERY_WAIT_TIME} msec), but we cannot wait any more.
746 *
747 * This looks up in-memory fallback cache and use it when available. If not, it just calls
748 * {@link #onCustomRingQueryComplete()} with default ringtone ("Send to voicemail" flag will
749 * be just ignored).
750 *
751 * @param number The phone number used for the async query. This method will take care of
752 * formatting or normalization of the number.
753 */
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700754 private void onCustomRingtoneQueryTimeout(Connection c) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700755 // First of all, this case itself should be rare enough, though we cannot avoid it in
756 // some situations (e.g. IPC is slow due to system overload, database is in sync, etc.)
757 Log.w(LOG_TAG, "CallerInfo query took too long; look up local fallback cache.");
758
759 // This method is intentionally verbose for now to detect possible bad side-effect for it.
760 // TODO: Remove the verbose log when it looks stable and reliable enough.
761
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700762
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700763 if (c != null) {
764 final CallerInfoCache.CacheEntry entry =
765 mApplication.callerInfoCache.getCacheEntry(c.getAddress());
766 if (entry != null) {
767 if (entry.sendToVoicemail) {
768 log("send to voicemail flag detected (in fallback cache). hanging up.");
769 if (mCM.getFirstActiveRingingCall().getLatestConnection() == c) {
770 PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
771 return;
772 }
773 }
774
775 if (entry.customRingtone != null) {
776 log("custom ringtone found (in fallback cache), setting up ringer: "
777 + entry.customRingtone);
778 this.mRinger.setCustomRingtoneUri(Uri.parse(entry.customRingtone));
779 }
780 } else {
781 // In this case we call onCustomRingQueryComplete(), just
782 // like if the query had completed normally. (But we're
783 // going to get the default ringtone, since we never got
784 // the chance to call Ringer.setCustomRingtoneUri()).
785 log("Failed to find fallback cache. Use default ringer tone.");
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700786 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700787 }
788
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700789 onCustomRingQueryComplete(c);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700790 }
791
792 private void onDisconnect(AsyncResult r) {
793 if (VDBG) log("onDisconnect()... CallManager state: " + mCM.getState());
794
795 mVoicePrivacyState = false;
796 Connection c = (Connection) r.result;
797 if (c != null) {
Anders Kristensen0b35f042014-02-27 14:31:07 -0800798 log("onDisconnect: cause = " + DisconnectCause.toString(c.getDisconnectCause())
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700799 + ", incoming = " + c.isIncoming()
800 + ", date = " + c.getCreateTime());
801 } else {
802 Log.w(LOG_TAG, "onDisconnect: null connection");
803 }
804
805 int autoretrySetting = 0;
806 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
807 autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
808 getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
809 }
810
811 // Stop any signalInfo tone being played when a call gets ended
812 stopSignalInfoTone();
813
814 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
815 // Resetting the CdmaPhoneCallState members
816 mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700817 }
818
819 // Stop the ringer if it was ringing (for an incoming call that
820 // either disconnected by itself, or was rejected by the user.)
821 //
822 // TODO: We technically *shouldn't* stop the ringer if the
823 // foreground or background call disconnects while an incoming call
824 // is still ringing, but that's a really rare corner case.
825 // It's safest to just unconditionally stop the ringer here.
826
827 // CDMA: For Call collision cases i.e. when the user makes an out going call
828 // and at the same time receives an Incoming Call, the Incoming Call is given
829 // higher preference. At this time framework sends a disconnect for the Out going
830 // call connection hence we should *not* be stopping the ringer being played for
831 // the Incoming Call
832 Call ringingCall = mCM.getFirstActiveRingingCall();
833 if (ringingCall.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
Santos Cordon352ff652014-05-30 01:41:45 -0700834 if (!PhoneUtils.isRealIncomingCall(ringingCall.getState())) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700835 if (DBG) log("stopRing()... (onDisconnect)");
836 mRinger.stopRing();
837 }
838 } else { // GSM
839 if (DBG) log("stopRing()... (onDisconnect)");
840 mRinger.stopRing();
841 }
842
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700843 // If this is the end of an OTASP call, pass it on to the PhoneApp.
844 if (c != null && TelephonyCapabilities.supportsOtasp(c.getCall().getPhone())) {
845 final String number = c.getAddress();
846 if (c.getCall().getPhone().isOtaSpNumber(number)) {
847 if (DBG) log("onDisconnect: this was an OTASP call!");
848 mApplication.handleOtaspDisconnect();
849 }
850 }
851
852 // Check for the various tones we might need to play (thru the
853 // earpiece) after a call disconnects.
854 int toneToPlay = InCallTonePlayer.TONE_NONE;
855
856 // The "Busy" or "Congestion" tone is the highest priority:
857 if (c != null) {
Anders Kristensen0b35f042014-02-27 14:31:07 -0800858 int cause = c.getDisconnectCause();
859 if (cause == DisconnectCause.BUSY) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700860 if (DBG) log("- need to play BUSY tone!");
861 toneToPlay = InCallTonePlayer.TONE_BUSY;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800862 } else if (cause == DisconnectCause.CONGESTION) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700863 if (DBG) log("- need to play CONGESTION tone!");
864 toneToPlay = InCallTonePlayer.TONE_CONGESTION;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800865 } else if (((cause == DisconnectCause.NORMAL)
866 || (cause == DisconnectCause.LOCAL))
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700867 && (mApplication.isOtaCallInActiveState())) {
868 if (DBG) log("- need to play OTA_CALL_END tone!");
869 toneToPlay = InCallTonePlayer.TONE_OTA_CALL_END;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800870 } else if (cause == DisconnectCause.CDMA_REORDER) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700871 if (DBG) log("- need to play CDMA_REORDER tone!");
872 toneToPlay = InCallTonePlayer.TONE_REORDER;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800873 } else if (cause == DisconnectCause.CDMA_INTERCEPT) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700874 if (DBG) log("- need to play CDMA_INTERCEPT tone!");
875 toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800876 } else if (cause == DisconnectCause.CDMA_DROP) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700877 if (DBG) log("- need to play CDMA_DROP tone!");
878 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800879 } else if (cause == DisconnectCause.OUT_OF_SERVICE) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700880 if (DBG) log("- need to play OUT OF SERVICE tone!");
881 toneToPlay = InCallTonePlayer.TONE_OUT_OF_SERVICE;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800882 } else if (cause == DisconnectCause.UNOBTAINABLE_NUMBER) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700883 if (DBG) log("- need to play TONE_UNOBTAINABLE_NUMBER tone!");
884 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
Anders Kristensen0b35f042014-02-27 14:31:07 -0800885 } else if (cause == DisconnectCause.ERROR_UNSPECIFIED) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700886 if (DBG) log("- DisconnectCause is ERROR_UNSPECIFIED: play TONE_CALL_ENDED!");
887 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
888 }
889 }
890
891 // If we don't need to play BUSY or CONGESTION, then play the
892 // "call ended" tone if this was a "regular disconnect" (i.e. a
893 // normal call where one end or the other hung up) *and* this
894 // disconnect event caused the phone to become idle. (In other
895 // words, we *don't* play the sound if one call hangs up but
896 // there's still an active call on the other line.)
897 // TODO: We may eventually want to disable this via a preference.
898 if ((toneToPlay == InCallTonePlayer.TONE_NONE)
899 && (mCM.getState() == PhoneConstants.State.IDLE)
900 && (c != null)) {
Anders Kristensen0b35f042014-02-27 14:31:07 -0800901 int cause = c.getDisconnectCause();
902 if ((cause == DisconnectCause.NORMAL) // remote hangup
903 || (cause == DisconnectCause.LOCAL)) { // local hangup
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700904 if (VDBG) log("- need to play CALL_ENDED tone!");
905 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
906 mIsCdmaRedialCall = false;
907 }
908 }
909
910 // All phone calls are disconnected.
911 if (mCM.getState() == PhoneConstants.State.IDLE) {
912 // Don't reset the audio mode or bluetooth/speakerphone state
913 // if we still need to let the user hear a tone through the earpiece.
914 if (toneToPlay == InCallTonePlayer.TONE_NONE) {
915 resetAudioStateAfterDisconnect();
916 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700917 }
918
919 if (c != null) {
920 mCallLogger.logCall(c);
921
922 final String number = c.getAddress();
923 final Phone phone = c.getCall().getPhone();
924 final boolean isEmergencyNumber =
Yorke Lee36bb2542014-06-05 08:09:52 -0700925 PhoneNumberUtils.isLocalEmergencyNumber(mApplication, number);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700926
927 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
928 if ((isEmergencyNumber)
929 && (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF)) {
930 if (mEmergencyTonePlayerVibrator != null) {
931 mEmergencyTonePlayerVibrator.stop();
932 }
933 }
934 }
935
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700936 // Possibly play a "post-disconnect tone" thru the earpiece.
937 // We do this here, rather than from the InCallScreen
938 // activity, since we need to do this even if you're not in
939 // the Phone UI at the moment the connection ends.
940 if (toneToPlay != InCallTonePlayer.TONE_NONE) {
941 if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
942 new InCallTonePlayer(toneToPlay).start();
943
944 // TODO: alternatively, we could start an InCallTonePlayer
945 // here with an "unlimited" tone length,
946 // and manually stop it later when this connection truly goes
947 // away. (The real connection over the network was closed as soon
948 // as we got the BUSY message. But our telephony layer keeps the
949 // connection open for a few extra seconds so we can show the
950 // "busy" indication to the user. We could stop the busy tone
951 // when *that* connection's "disconnect" event comes in.)
952 }
953
Santos Cordonf68db2e2014-07-02 14:40:44 -0700954 final int cause = c.getDisconnectCause();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700955 if (((mPreviousCdmaCallState == Call.State.DIALING)
956 || (mPreviousCdmaCallState == Call.State.ALERTING))
957 && (!isEmergencyNumber)
Anders Kristensen0b35f042014-02-27 14:31:07 -0800958 && (cause != DisconnectCause.INCOMING_MISSED )
959 && (cause != DisconnectCause.NORMAL)
960 && (cause != DisconnectCause.LOCAL)
961 && (cause != DisconnectCause.INCOMING_REJECTED)) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700962 if (!mIsCdmaRedialCall) {
963 if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
964 // TODO: (Moto): The contact reference data may need to be stored and use
965 // here when redialing a call. For now, pass in NULL as the URI parameter.
Santos Cordonce02f3a2013-09-19 01:58:42 -0700966 final int status =
967 PhoneUtils.placeCall(mApplication, phone, number, null, false);
968 if (status != PhoneUtils.CALL_STATUS_FAILED) {
969 mIsCdmaRedialCall = true;
970 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700971 } else {
972 mIsCdmaRedialCall = false;
973 }
974 } else {
975 mIsCdmaRedialCall = false;
976 }
977 }
978 }
979 }
980
981 /**
982 * Resets the audio mode and speaker state when a call ends.
983 */
984 private void resetAudioStateAfterDisconnect() {
985 if (VDBG) log("resetAudioStateAfterDisconnect()...");
986
987 if (mBluetoothHeadset != null) {
988 mBluetoothHeadset.disconnectAudio();
989 }
990
991 // call turnOnSpeaker() with state=false and store=true even if speaker
992 // is already off to reset user requested speaker state.
993 PhoneUtils.turnOnSpeaker(mApplication, false, true);
994
995 PhoneUtils.setAudioMode(mCM);
996 }
997
998 private void onMwiChanged(boolean visible) {
999 if (VDBG) log("onMwiChanged(): " + visible);
1000
1001 // "Voicemail" is meaningless on non-voice-capable devices,
1002 // so ignore MWI events.
1003 if (!PhoneGlobals.sVoiceCapable) {
1004 // ...but still log a warning, since we shouldn't have gotten this
1005 // event in the first place!
1006 // (PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR events
1007 // *should* be blocked at the telephony layer on non-voice-capable
1008 // capable devices.)
1009 Log.w(LOG_TAG, "Got onMwiChanged() on non-voice-capable device! Ignoring...");
1010 return;
1011 }
1012
1013 mApplication.notificationMgr.updateMwi(visible);
1014 }
1015
1016 /**
1017 * Posts a delayed PHONE_MWI_CHANGED event, to schedule a "retry" for a
1018 * failed NotificationMgr.updateMwi() call.
1019 */
1020 /* package */ void sendMwiChangedDelayed(long delayMillis) {
1021 Message message = Message.obtain(this, PHONE_MWI_CHANGED);
1022 sendMessageDelayed(message, delayMillis);
1023 }
1024
1025 private void onCfiChanged(boolean visible) {
1026 if (VDBG) log("onCfiChanged(): " + visible);
1027 mApplication.notificationMgr.updateCfi(visible);
1028 }
1029
1030 /**
1031 * Indicates whether or not this ringer is ringing.
1032 */
1033 boolean isRinging() {
1034 return mRinger.isRinging();
1035 }
1036
1037 /**
1038 * Stops the current ring, and tells the notifier that future
1039 * ring requests should be ignored.
1040 */
1041 void silenceRinger() {
1042 mSilentRingerRequested = true;
1043 if (DBG) log("stopRing()... (silenceRinger)");
1044 mRinger.stopRing();
1045 }
1046
1047 /**
1048 * Restarts the ringer after having previously silenced it.
1049 *
1050 * (This is a no-op if the ringer is actually still ringing, or if the
1051 * incoming ringing call no longer exists.)
1052 */
1053 /* package */ void restartRinger() {
1054 if (DBG) log("restartRinger()...");
1055 // Already ringing or Silent requested; no need to restart.
1056 if (isRinging() || mSilentRingerRequested) return;
1057
1058 final Call ringingCall = mCM.getFirstActiveRingingCall();
1059 // Don't check ringingCall.isRinging() here, since that'll be true
1060 // for the WAITING state also. We only allow the ringer for
1061 // regular INCOMING calls.
1062 if (DBG) log("- ringingCall state: " + ringingCall.getState());
1063 if (ringingCall.getState() == Call.State.INCOMING) {
1064 mRinger.ring();
1065 }
1066 }
1067
1068 /**
1069 * Helper class to play tones through the earpiece (or speaker / BT)
1070 * during a call, using the ToneGenerator.
1071 *
1072 * To use, just instantiate a new InCallTonePlayer
1073 * (passing in the TONE_* constant for the tone you want)
1074 * and start() it.
1075 *
1076 * When we're done playing the tone, if the phone is idle at that
1077 * point, we'll reset the audio routing and speaker state.
1078 * (That means that for tones that get played *after* a call
1079 * disconnects, like "busy" or "congestion" or "call ended", you
1080 * should NOT call resetAudioStateAfterDisconnect() yourself.
1081 * Instead, just start the InCallTonePlayer, which will automatically
1082 * defer the resetAudioStateAfterDisconnect() call until the tone
1083 * finishes playing.)
1084 */
1085 private class InCallTonePlayer extends Thread {
1086 private int mToneId;
1087 private int mState;
1088 // The possible tones we can play.
1089 public static final int TONE_NONE = 0;
1090 public static final int TONE_CALL_WAITING = 1;
1091 public static final int TONE_BUSY = 2;
1092 public static final int TONE_CONGESTION = 3;
1093 public static final int TONE_CALL_ENDED = 4;
1094 public static final int TONE_VOICE_PRIVACY = 5;
1095 public static final int TONE_REORDER = 6;
1096 public static final int TONE_INTERCEPT = 7;
1097 public static final int TONE_CDMA_DROP = 8;
1098 public static final int TONE_OUT_OF_SERVICE = 9;
1099 public static final int TONE_REDIAL = 10;
1100 public static final int TONE_OTA_CALL_END = 11;
1101 public static final int TONE_RING_BACK = 12;
1102 public static final int TONE_UNOBTAINABLE_NUMBER = 13;
1103
1104 // The tone volume relative to other sounds in the stream
1105 static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
1106 static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
1107 static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
1108
1109 // Buffer time (in msec) to add on to tone timeout value.
1110 // Needed mainly when the timeout value for a tone is the
1111 // exact duration of the tone itself.
1112 static final int TONE_TIMEOUT_BUFFER = 20;
1113
1114 // The tone state
1115 static final int TONE_OFF = 0;
1116 static final int TONE_ON = 1;
1117 static final int TONE_STOPPED = 2;
1118
1119 InCallTonePlayer(int toneId) {
1120 super();
1121 mToneId = toneId;
1122 mState = TONE_OFF;
1123 }
1124
1125 @Override
1126 public void run() {
1127 log("InCallTonePlayer.run(toneId = " + mToneId + ")...");
1128
1129 int toneType = 0; // passed to ToneGenerator.startTone()
1130 int toneVolume; // passed to the ToneGenerator constructor
1131 int toneLengthMillis;
1132 int phoneType = mCM.getFgPhone().getPhoneType();
1133
1134 switch (mToneId) {
1135 case TONE_CALL_WAITING:
1136 toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
1137 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1138 // Call waiting tone is stopped by stopTone() method
1139 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
1140 break;
1141 case TONE_BUSY:
1142 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1143 toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
1144 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1145 toneLengthMillis = 1000;
Sailesh Nepalcc0375f2013-11-13 09:15:18 -08001146 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM
1147 || phoneType == PhoneConstants.PHONE_TYPE_SIP
Etan Cohen0ca1c802014-07-07 15:35:48 -07001148 || phoneType == PhoneConstants.PHONE_TYPE_IMS
Sailesh Nepalcc0375f2013-11-13 09:15:18 -08001149 || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001150 toneType = ToneGenerator.TONE_SUP_BUSY;
1151 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1152 toneLengthMillis = 4000;
1153 } else {
1154 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1155 }
1156 break;
1157 case TONE_CONGESTION:
1158 toneType = ToneGenerator.TONE_SUP_CONGESTION;
1159 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1160 toneLengthMillis = 4000;
1161 break;
1162
1163 case TONE_CALL_ENDED:
1164 toneType = ToneGenerator.TONE_PROP_PROMPT;
1165 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1166 toneLengthMillis = 200;
1167 break;
1168 case TONE_OTA_CALL_END:
1169 if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone ==
1170 OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) {
1171 toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD;
1172 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1173 toneLengthMillis = 750;
1174 } else {
1175 toneType = ToneGenerator.TONE_PROP_PROMPT;
1176 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1177 toneLengthMillis = 200;
1178 }
1179 break;
1180 case TONE_VOICE_PRIVACY:
1181 toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
1182 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1183 toneLengthMillis = 5000;
1184 break;
1185 case TONE_REORDER:
1186 toneType = ToneGenerator.TONE_CDMA_REORDER;
1187 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1188 toneLengthMillis = 4000;
1189 break;
1190 case TONE_INTERCEPT:
1191 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
1192 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1193 toneLengthMillis = 500;
1194 break;
1195 case TONE_CDMA_DROP:
1196 case TONE_OUT_OF_SERVICE:
1197 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
1198 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1199 toneLengthMillis = 375;
1200 break;
1201 case TONE_REDIAL:
1202 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
1203 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1204 toneLengthMillis = 5000;
1205 break;
1206 case TONE_RING_BACK:
1207 toneType = ToneGenerator.TONE_SUP_RINGTONE;
1208 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1209 // Call ring back tone is stopped by stopTone() method
1210 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
1211 break;
1212 case TONE_UNOBTAINABLE_NUMBER:
1213 toneType = ToneGenerator.TONE_SUP_ERROR;
1214 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1215 toneLengthMillis = 4000;
1216 break;
1217 default:
1218 throw new IllegalArgumentException("Bad toneId: " + mToneId);
1219 }
1220
1221 // If the mToneGenerator creation fails, just continue without it. It is
1222 // a local audio signal, and is not as important.
1223 ToneGenerator toneGenerator;
1224 try {
1225 int stream;
1226 if (mBluetoothHeadset != null) {
1227 stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
1228 AudioManager.STREAM_VOICE_CALL;
1229 } else {
1230 stream = AudioManager.STREAM_VOICE_CALL;
1231 }
1232 toneGenerator = new ToneGenerator(stream, toneVolume);
1233 // if (DBG) log("- created toneGenerator: " + toneGenerator);
1234 } catch (RuntimeException e) {
1235 Log.w(LOG_TAG,
1236 "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
1237 toneGenerator = null;
1238 }
1239
1240 // Using the ToneGenerator (with the CALL_WAITING / BUSY /
1241 // CONGESTION tones at least), the ToneGenerator itself knows
1242 // the right pattern of tones to play; we do NOT need to
1243 // manually start/stop each individual tone, or manually
1244 // insert the correct delay between tones. (We just start it
1245 // and let it run for however long we want the tone pattern to
1246 // continue.)
1247 //
1248 // TODO: When we stop the ToneGenerator in the middle of a
1249 // "tone pattern", it sounds bad if we cut if off while the
1250 // tone is actually playing. Consider adding API to the
1251 // ToneGenerator to say "stop at the next silent part of the
1252 // pattern", or simply "play the pattern N times and then
1253 // stop."
1254 boolean needToStopTone = true;
1255 boolean okToPlayTone = false;
1256
1257 if (toneGenerator != null) {
1258 int ringerMode = mAudioManager.getRingerMode();
1259 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1260 if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) {
1261 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
1262 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
1263 if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType);
1264 okToPlayTone = true;
1265 needToStopTone = false;
1266 }
1267 } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) ||
1268 (toneType == ToneGenerator.TONE_CDMA_REORDER) ||
1269 (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) ||
1270 (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) ||
1271 (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) {
1272 if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
1273 if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType);
1274 okToPlayTone = true;
1275 needToStopTone = false;
1276 }
1277 } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) ||
1278 (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) {
1279 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
1280 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
1281 if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType);
1282 okToPlayTone = true;
1283 needToStopTone = false;
1284 }
1285 } else { // For the rest of the tones, always OK to play.
1286 okToPlayTone = true;
1287 }
1288 } else { // Not "CDMA"
1289 okToPlayTone = true;
1290 }
1291
1292 synchronized (this) {
1293 if (okToPlayTone && mState != TONE_STOPPED) {
1294 mState = TONE_ON;
1295 toneGenerator.startTone(toneType);
1296 try {
1297 wait(toneLengthMillis + TONE_TIMEOUT_BUFFER);
1298 } catch (InterruptedException e) {
1299 Log.w(LOG_TAG,
1300 "InCallTonePlayer stopped: " + e);
1301 }
1302 if (needToStopTone) {
1303 toneGenerator.stopTone();
1304 }
1305 }
1306 // if (DBG) log("- InCallTonePlayer: done playing.");
1307 toneGenerator.release();
1308 mState = TONE_OFF;
1309 }
1310 }
1311
1312 // Finally, do the same cleanup we otherwise would have done
1313 // in onDisconnect().
1314 //
1315 // (But watch out: do NOT do this if the phone is in use,
1316 // since some of our tones get played *during* a call (like
1317 // CALL_WAITING) and we definitely *don't*
1318 // want to reset the audio mode / speaker / bluetooth after
1319 // playing those!
1320 // This call is really here for use with tones that get played
1321 // *after* a call disconnects, like "busy" or "congestion" or
1322 // "call ended", where the phone has already become idle but
1323 // we need to defer the resetAudioStateAfterDisconnect() call
1324 // till the tone finishes playing.)
1325 if (mCM.getState() == PhoneConstants.State.IDLE) {
1326 resetAudioStateAfterDisconnect();
1327 }
1328 }
1329
1330 public void stopTone() {
1331 synchronized (this) {
1332 if (mState == TONE_ON) {
1333 notify();
1334 }
1335 mState = TONE_STOPPED;
1336 }
1337 }
1338 }
1339
1340 /**
1341 * Displays a notification when the phone receives a DisplayInfo record.
1342 */
1343 private void onDisplayInfo(AsyncResult r) {
1344 // Extract the DisplayInfo String from the message
1345 CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
1346
1347 if (displayInfoRec != null) {
1348 String displayInfo = displayInfoRec.alpha;
1349 if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
1350 CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
1351
1352 // start a 2 second timer
1353 sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
1354 DISPLAYINFO_NOTIFICATION_TIME);
1355 }
1356 }
1357
1358 /**
1359 * Helper class to play SignalInfo tones using the ToneGenerator.
1360 *
1361 * To use, just instantiate a new SignalInfoTonePlayer
1362 * (passing in the ToneID constant for the tone you want)
1363 * and start() it.
1364 */
1365 private class SignalInfoTonePlayer extends Thread {
1366 private int mToneId;
1367
1368 SignalInfoTonePlayer(int toneId) {
1369 super();
1370 mToneId = toneId;
1371 }
1372
1373 @Override
1374 public void run() {
1375 log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
1376
1377 if (mSignalInfoToneGenerator != null) {
1378 //First stop any ongoing SignalInfo tone
1379 mSignalInfoToneGenerator.stopTone();
1380
1381 //Start playing the new tone if its a valid tone
1382 mSignalInfoToneGenerator.startTone(mToneId);
1383 }
1384 }
1385 }
1386
1387 /**
1388 * Plays a tone when the phone receives a SignalInfo record.
1389 */
1390 private void onSignalInfo(AsyncResult r) {
1391 // Signal Info are totally ignored on non-voice-capable devices.
1392 if (!PhoneGlobals.sVoiceCapable) {
1393 Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring...");
1394 return;
1395 }
1396
1397 if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) {
1398 // Do not start any new SignalInfo tone when Call state is INCOMING
1399 // and stop any previous SignalInfo tone which is being played
1400 stopSignalInfoTone();
1401 } else {
1402 // Extract the SignalInfo String from the message
1403 CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
1404 // Only proceed if a Signal info is present.
1405 if (signalInfoRec != null) {
1406 boolean isPresent = signalInfoRec.isPresent;
1407 if (DBG) log("onSignalInfo: isPresent=" + isPresent);
1408 if (isPresent) {// if tone is valid
1409 int uSignalType = signalInfoRec.signalType;
1410 int uAlertPitch = signalInfoRec.alertPitch;
1411 int uSignal = signalInfoRec.signal;
1412
1413 if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
1414 uAlertPitch + ", uSignal=" + uSignal);
1415 //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1416 int toneID = SignalToneUtil.getAudioToneFromSignalInfo
1417 (uSignalType, uAlertPitch, uSignal);
1418
1419 //Create the SignalInfo tone player and pass the ToneID
1420 new SignalInfoTonePlayer(toneID).start();
1421 }
1422 }
1423 }
1424 }
1425
1426 /**
1427 * Stops a SignalInfo tone in the following condition
1428 * 1 - On receiving a New Ringing Call
1429 * 2 - On disconnecting a call
1430 * 3 - On answering a Call Waiting Call
1431 */
1432 /* package */ void stopSignalInfoTone() {
1433 if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player");
1434 new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
1435 }
1436
1437 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001438 * Return the private variable mPreviousCdmaCallState.
1439 */
1440 /* package */ Call.State getPreviousCdmaCallState() {
1441 return mPreviousCdmaCallState;
1442 }
1443
1444 /**
1445 * Return the private variable mVoicePrivacyState.
1446 */
1447 /* package */ boolean getVoicePrivacyState() {
1448 return mVoicePrivacyState;
1449 }
1450
1451 /**
1452 * Return the private variable mIsCdmaRedialCall.
1453 */
1454 /* package */ boolean getIsCdmaRedialCall() {
1455 return mIsCdmaRedialCall;
1456 }
1457
1458 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001459 * Inner class to handle emergency call tone and vibrator
1460 */
1461 private class EmergencyTonePlayerVibrator {
1462 private final int EMG_VIBRATE_LENGTH = 1000; // ms.
1463 private final int EMG_VIBRATE_PAUSE = 1000; // ms.
1464 private final long[] mVibratePattern =
1465 new long[] { EMG_VIBRATE_LENGTH, EMG_VIBRATE_PAUSE };
1466
1467 private ToneGenerator mToneGenerator;
1468 // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this vibrator
1469 // object will be isolated from others.
1470 private Vibrator mEmgVibrator = new SystemVibrator();
1471 private int mInCallVolume;
1472
1473 /**
1474 * constructor
1475 */
1476 public EmergencyTonePlayerVibrator() {
1477 }
1478
1479 /**
1480 * Start the emergency tone or vibrator.
1481 */
1482 private void start() {
1483 if (VDBG) log("call startEmergencyToneOrVibrate.");
1484 int ringerMode = mAudioManager.getRingerMode();
1485
1486 if ((mIsEmergencyToneOn == EMERGENCY_TONE_ALERT) &&
1487 (ringerMode == AudioManager.RINGER_MODE_NORMAL)) {
1488 log("EmergencyTonePlayerVibrator.start(): emergency tone...");
1489 mToneGenerator = new ToneGenerator (AudioManager.STREAM_VOICE_CALL,
1490 InCallTonePlayer.TONE_RELATIVE_VOLUME_EMERGENCY);
1491 if (mToneGenerator != null) {
1492 mInCallVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1493 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1494 mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL),
1495 0);
1496 mToneGenerator.startTone(ToneGenerator.TONE_CDMA_EMERGENCY_RINGBACK);
1497 mCurrentEmergencyToneState = EMERGENCY_TONE_ALERT;
1498 }
1499 } else if (mIsEmergencyToneOn == EMERGENCY_TONE_VIBRATE) {
1500 log("EmergencyTonePlayerVibrator.start(): emergency vibrate...");
1501 if (mEmgVibrator != null) {
John Spurlock6ee06d02014-07-18 20:06:20 -04001502 mEmgVibrator.vibrate(mVibratePattern, 0, VIBRATION_ATTRIBUTES);
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001503 mCurrentEmergencyToneState = EMERGENCY_TONE_VIBRATE;
1504 }
1505 }
1506 }
1507
1508 /**
1509 * If the emergency tone is active, stop the tone or vibrator accordingly.
1510 */
1511 private void stop() {
1512 if (VDBG) log("call stopEmergencyToneOrVibrate.");
1513
1514 if ((mCurrentEmergencyToneState == EMERGENCY_TONE_ALERT)
1515 && (mToneGenerator != null)) {
1516 mToneGenerator.stopTone();
1517 mToneGenerator.release();
1518 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1519 mInCallVolume,
1520 0);
1521 } else if ((mCurrentEmergencyToneState == EMERGENCY_TONE_VIBRATE)
1522 && (mEmgVibrator != null)) {
1523 mEmgVibrator.cancel();
1524 }
1525 mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
1526 }
1527 }
1528
1529 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
1530 new BluetoothProfile.ServiceListener() {
1531 public void onServiceConnected(int profile, BluetoothProfile proxy) {
1532 mBluetoothHeadset = (BluetoothHeadset) proxy;
1533 if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
1534 }
1535
1536 public void onServiceDisconnected(int profile) {
1537 mBluetoothHeadset = null;
1538 }
1539 };
1540
1541 private void onRingbackTone(AsyncResult r) {
1542 boolean playTone = (Boolean)(r.result);
1543
1544 if (playTone == true) {
1545 // Only play when foreground call is in DIALING or ALERTING.
1546 // to prevent a late coming playtone after ALERTING.
1547 // Don't play ringback tone if it is in play, otherwise it will cut
1548 // the current tone and replay it
1549 if (mCM.getActiveFgCallState().isDialing() &&
1550 mInCallRingbackTonePlayer == null) {
1551 mInCallRingbackTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_RING_BACK);
1552 mInCallRingbackTonePlayer.start();
1553 }
1554 } else {
1555 if (mInCallRingbackTonePlayer != null) {
1556 mInCallRingbackTonePlayer.stopTone();
1557 mInCallRingbackTonePlayer = null;
1558 }
1559 }
1560 }
1561
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001562 private void log(String msg) {
1563 Log.d(LOG_TAG, msg);
1564 }
1565}