blob: 669f9b035830c03cb8c4bdafd4bb8c08c1d283b8 [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;
28import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
29import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
30import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
31import com.android.internal.telephony.cdma.SignalToneUtil;
32
33import android.app.ActivityManagerNative;
34import android.bluetooth.BluetoothAdapter;
35import android.bluetooth.BluetoothHeadset;
36import android.bluetooth.BluetoothProfile;
37import android.content.Context;
38import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.AsyncResult;
42import android.os.Handler;
43import android.os.Message;
44import android.os.RemoteException;
45import android.os.SystemProperties;
46import android.os.SystemVibrator;
47import android.os.Vibrator;
48import android.provider.CallLog.Calls;
49import android.provider.Settings;
50import 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
73 // Timers related to CDMA Call Waiting
74 // 1) For displaying Caller Info
75 // 2) For disabling "Add Call" menu option once User selects Ignore or CW Timeout occures
76 private static final int CALLWAITING_CALLERINFO_DISPLAY_TIME = 20000; // msec
77 private static final int CALLWAITING_ADDCALL_DISABLE_TIME = 30000; // msec
78
79 // Time to display the DisplayInfo Record sent by CDMA network
80 private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
81
82 /** The singleton instance. */
83 private static CallNotifier sInstance;
84
85 // Boolean to keep track of whether or not a CDMA Call Waiting call timed out.
86 //
87 // This is CDMA-specific, because with CDMA we *don't* get explicit
88 // notification from the telephony layer that a call-waiting call has
89 // stopped ringing. Instead, when a call-waiting call first comes in we
90 // start a 20-second timer (see CALLWAITING_CALLERINFO_DISPLAY_DONE), and
91 // if the timer expires we clean up the call and treat it as a missed call.
92 //
93 // If this field is true, that means that the current Call Waiting call
94 // "timed out" and should be logged in Call Log as a missed call. If it's
95 // false when we reach onCdmaCallWaitingReject(), we can assume the user
96 // explicitly rejected this call-waiting call.
97 //
98 // This field is reset to false any time a call-waiting call first comes
99 // in, and after cleaning up a missed call-waiting call. It's only ever
100 // set to true when the CALLWAITING_CALLERINFO_DISPLAY_DONE timer fires.
101 //
102 // TODO: do we really need a member variable for this? Don't we always
103 // know at the moment we call onCdmaCallWaitingReject() whether this is an
104 // explicit rejection or not?
105 // (Specifically: when we call onCdmaCallWaitingReject() from
106 // PhoneUtils.hangupRingingCall() that means the user deliberately rejected
107 // the call, and if we call onCdmaCallWaitingReject() because of a
108 // CALLWAITING_CALLERINFO_DISPLAY_DONE event that means that it timed
109 // out...)
110 private boolean mCallWaitingTimeOut = false;
111
112 // values used to track the query state
113 private static final int CALLERINFO_QUERY_READY = 0;
114 private static final int CALLERINFO_QUERYING = -1;
115
116 // the state of the CallerInfo Query.
117 private int mCallerInfoQueryState;
118
119 // object used to synchronize access to mCallerInfoQueryState
120 private Object mCallerInfoQueryStateGuard = new Object();
121
122 // Event used to indicate a query timeout.
123 private static final int RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT = 100;
124
125 // Events generated internally:
126 private static final int PHONE_MWI_CHANGED = 21;
127 private static final int CALLWAITING_CALLERINFO_DISPLAY_DONE = 22;
128 private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 23;
129 private static final int DISPLAYINFO_NOTIFICATION_DONE = 24;
130 private static final int CDMA_CALL_WAITING_REJECT = 26;
131 private static final int UPDATE_IN_CALL_NOTIFICATION = 27;
132
133 // Emergency call related defines:
134 private static final int EMERGENCY_TONE_OFF = 0;
135 private static final int EMERGENCY_TONE_ALERT = 1;
136 private static final int EMERGENCY_TONE_VIBRATE = 2;
137
138 private PhoneGlobals mApplication;
139 private CallManager mCM;
140 private CallStateMonitor mCallStateMonitor;
141 private Ringer mRinger;
142 private BluetoothHeadset mBluetoothHeadset;
143 private CallLogger mCallLogger;
144 private boolean mSilentRingerRequested;
145
146 // ToneGenerator instance for playing SignalInfo tones
147 private ToneGenerator mSignalInfoToneGenerator;
148
149 // The tone volume relative to other sounds in the stream SignalInfo
150 private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80;
151
152 private Call.State mPreviousCdmaCallState;
153 private boolean mVoicePrivacyState = false;
154 private boolean mIsCdmaRedialCall = false;
155
156 // Emergency call tone and vibrate:
157 private int mIsEmergencyToneOn;
158 private int mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
159 private EmergencyTonePlayerVibrator mEmergencyTonePlayerVibrator;
160
161 // Ringback tone player
162 private InCallTonePlayer mInCallRingbackTonePlayer;
163
164 // Call waiting tone player
165 private InCallTonePlayer mCallWaitingTonePlayer;
166
167 // Cached AudioManager
168 private AudioManager mAudioManager;
169
170 /**
171 * Initialize the singleton CallNotifier instance.
172 * This is only done once, at startup, from PhoneApp.onCreate().
173 */
174 /* package */ static CallNotifier init(PhoneGlobals app, Phone phone, Ringer ringer,
175 CallLogger callLogger, CallStateMonitor callStateMonitor) {
176 synchronized (CallNotifier.class) {
177 if (sInstance == null) {
178 sInstance = new CallNotifier(app, phone, ringer, callLogger, callStateMonitor);
179 } else {
180 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
181 }
182 return sInstance;
183 }
184 }
185
186 /** Private constructor; @see init() */
187 private CallNotifier(PhoneGlobals app, Phone phone, Ringer ringer, CallLogger callLogger,
188 CallStateMonitor callStateMonitor) {
189 mApplication = app;
190 mCM = app.mCM;
191 mCallLogger = callLogger;
192 mCallStateMonitor = callStateMonitor;
193
194 mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
195
196 callStateMonitor.addListener(this);
197
198 createSignalInfoToneGenerator();
199
200 mRinger = ringer;
201 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
202 if (adapter != null) {
203 adapter.getProfileProxy(mApplication.getApplicationContext(),
204 mBluetoothProfileServiceListener,
205 BluetoothProfile.HEADSET);
206 }
207
208 TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
209 Context.TELEPHONY_SERVICE);
210 telephonyManager.listen(mPhoneStateListener,
211 PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
212 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
213 }
214
215 private void createSignalInfoToneGenerator() {
216 // Instantiate the ToneGenerator for SignalInfo and CallWaiting
217 // TODO: We probably don't need the mSignalInfoToneGenerator instance
218 // around forever. Need to change it so as to create a ToneGenerator instance only
219 // when a tone is being played and releases it after its done playing.
220 if (mSignalInfoToneGenerator == null) {
221 try {
222 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
223 TONE_RELATIVE_VOLUME_SIGNALINFO);
224 Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay");
225 } catch (RuntimeException e) {
226 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
227 "mSignalInfoToneGenerator: " + e);
228 mSignalInfoToneGenerator = null;
229 }
230 } else {
231 Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping");
232 }
233 }
234
235 @Override
236 public void handleMessage(Message msg) {
237 switch (msg.what) {
238 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
239 log("RINGING... (new)");
240 onNewRingingConnection((AsyncResult) msg.obj);
241 mSilentRingerRequested = false;
242 break;
243
244 case CallStateMonitor.PHONE_INCOMING_RING:
245 // repeat the ring when requested by the RIL, and when the user has NOT
246 // specifically requested silence.
247 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
248 PhoneBase pb = (PhoneBase)((AsyncResult)msg.obj).result;
249
250 if ((pb.getState() == PhoneConstants.State.RINGING)
251 && (mSilentRingerRequested == false)) {
252 if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
253 mRinger.ring();
254 } else {
255 if (DBG) log("RING before NEW_RING, skipping");
256 }
257 }
258 break;
259
260 case CallStateMonitor.PHONE_STATE_CHANGED:
261 onPhoneStateChanged((AsyncResult) msg.obj);
262 break;
263
264 case CallStateMonitor.PHONE_DISCONNECT:
265 if (DBG) log("DISCONNECT");
266 onDisconnect((AsyncResult) msg.obj);
267 break;
268
269 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
270 onUnknownConnectionAppeared((AsyncResult) msg.obj);
271 break;
272
273 case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
274 onCustomRingtoneQueryTimeout((String) msg.obj);
275 break;
276
277 case PHONE_MWI_CHANGED:
278 onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
279 break;
280
281 case CallStateMonitor.PHONE_CDMA_CALL_WAITING:
282 if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
283 onCdmaCallWaiting((AsyncResult) msg.obj);
284 break;
285
286 case CDMA_CALL_WAITING_REJECT:
287 Log.i(LOG_TAG, "Received CDMA_CALL_WAITING_REJECT event");
288 onCdmaCallWaitingReject();
289 break;
290
291 case CALLWAITING_CALLERINFO_DISPLAY_DONE:
292 Log.i(LOG_TAG, "Received CALLWAITING_CALLERINFO_DISPLAY_DONE event");
293 mCallWaitingTimeOut = true;
294 onCdmaCallWaitingReject();
295 break;
296
297 case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
298 if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
299 // Set the mAddCallMenuStateAfterCW state to true
300 mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
301 mApplication.updateInCallScreen();
302 break;
303
304 case CallStateMonitor.PHONE_STATE_DISPLAYINFO:
305 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
306 onDisplayInfo((AsyncResult) msg.obj);
307 break;
308
309 case CallStateMonitor.PHONE_STATE_SIGNALINFO:
310 if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
311 onSignalInfo((AsyncResult) msg.obj);
312 break;
313
314 case DISPLAYINFO_NOTIFICATION_DONE:
315 if (DBG) log("Received Display Info notification done event ...");
316 CdmaDisplayInfo.dismissDisplayInfoRecord();
317 break;
318
319 case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE:
320 if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
321 mApplication.handleOtaspEvent(msg);
322 break;
323
324 case CallStateMonitor.PHONE_ENHANCED_VP_ON:
325 if (DBG) log("PHONE_ENHANCED_VP_ON...");
326 if (!mVoicePrivacyState) {
327 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
328 new InCallTonePlayer(toneToPlay).start();
329 mVoicePrivacyState = true;
330 // Update the VP icon:
331 if (DBG) log("- updating notification for VP state...");
332 mApplication.notificationMgr.updateInCallNotification();
333 }
334 break;
335
336 case CallStateMonitor.PHONE_ENHANCED_VP_OFF:
337 if (DBG) log("PHONE_ENHANCED_VP_OFF...");
338 if (mVoicePrivacyState) {
339 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
340 new InCallTonePlayer(toneToPlay).start();
341 mVoicePrivacyState = false;
342 // Update the VP icon:
343 if (DBG) log("- updating notification for VP state...");
344 mApplication.notificationMgr.updateInCallNotification();
345 }
346 break;
347
348 case CallStateMonitor.PHONE_RINGBACK_TONE:
349 onRingbackTone((AsyncResult) msg.obj);
350 break;
351
352 case CallStateMonitor.PHONE_RESEND_MUTE:
353 onResendMute();
354 break;
355
356 case UPDATE_IN_CALL_NOTIFICATION:
357 mApplication.notificationMgr.updateInCallNotification();
358 break;
359
360 default:
361 // super.handleMessage(msg);
362 }
363 }
364
365 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
366 @Override
367 public void onMessageWaitingIndicatorChanged(boolean mwi) {
368 onMwiChanged(mwi);
369 }
370
371 @Override
372 public void onCallForwardingIndicatorChanged(boolean cfi) {
373 onCfiChanged(cfi);
374 }
375 };
376
377 /**
378 * Handles a "new ringing connection" event from the telephony layer.
379 */
380 private void onNewRingingConnection(AsyncResult r) {
381 Connection c = (Connection) r.result;
382 log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
383 Call ringing = c.getCall();
384 Phone phone = ringing.getPhone();
385
386 // Check for a few cases where we totally ignore incoming calls.
387 if (ignoreAllIncomingCalls(phone)) {
388 // Immediately reject the call, without even indicating to the user
389 // that an incoming call occurred. (This will generally send the
390 // caller straight to voicemail, just as if we *had* shown the
391 // incoming-call UI and the user had declined the call.)
392 PhoneUtils.hangupRingingCall(ringing);
393 return;
394 }
395
396 if (!c.isRinging()) {
397 Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
398 // This is a very strange case: an incoming call that stopped
399 // ringing almost instantly after the onNewRingingConnection()
400 // event. There's nothing we can do here, so just bail out
401 // without doing anything. (But presumably we'll log it in
402 // the call log when the disconnect event comes in...)
403 return;
404 }
405
406 // Stop any signalInfo tone being played on receiving a Call
407 stopSignalInfoTone();
408
409 Call.State state = c.getState();
410 // State will be either INCOMING or WAITING.
411 if (VDBG) log("- connection is ringing! state = " + state);
412 // if (DBG) PhoneUtils.dumpCallState(mPhone);
413
414 // No need to do any service state checks here (like for
415 // "emergency mode"), since in those states the SIM won't let
416 // us get incoming connections in the first place.
417
418 // TODO: Consider sending out a serialized broadcast Intent here
419 // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
420 // ringer and going to the in-call UI. The intent should contain
421 // the caller-id info for the current connection, and say whether
422 // it would be a "call waiting" call or a regular ringing call.
423 // If anybody consumed the broadcast, we'd bail out without
424 // ringing or bringing up the in-call UI.
425 //
426 // This would give 3rd party apps a chance to listen for (and
427 // intercept) new ringing connections. An app could reject the
428 // incoming call by consuming the broadcast and doing nothing, or
429 // it could "pick up" the call (without any action by the user!)
430 // via some future TelephonyManager API.
431 //
432 // See bug 1312336 for more details.
433 // We'd need to protect this with a new "intercept incoming calls"
434 // system permission.
435
436 // Obtain a partial wake lock to make sure the CPU doesn't go to
437 // sleep before we finish bringing up the InCallScreen.
438 // (This will be upgraded soon to a full wake lock; see
439 // showIncomingCall().)
440 if (VDBG) log("Holding wake lock on new incoming connection.");
441 mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL);
442
443 // - don't ring for call waiting connections
444 // - do this before showing the incoming call panel
445 if (PhoneUtils.isRealIncomingCall(state)) {
446 startIncomingCallQuery(c);
447 } else {
448 if (VDBG) log("- starting call waiting tone...");
449 if (mCallWaitingTonePlayer == null) {
450 mCallWaitingTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING);
451 mCallWaitingTonePlayer.start();
452 }
453 // in this case, just fall through like before, and call
454 // showIncomingCall().
455 if (DBG) log("- showing incoming call (this is a WAITING call)...");
456 showIncomingCall();
457 }
458
459 // Note we *don't* post a status bar notification here, since
460 // we're not necessarily ready to actually show the incoming call
461 // to the user. (For calls in the INCOMING state, at least, we
462 // still need to run a caller-id query, and we may not even ring
463 // at all if the "send directly to voicemail" flag is set.)
464 //
465 // Instead, we update the notification (and potentially launch the
466 // InCallScreen) from the showIncomingCall() method, which runs
467 // when the caller-id query completes or times out.
468
469 if (VDBG) log("- onNewRingingConnection() done.");
470 }
471
472 /**
473 * Determines whether or not we're allowed to present incoming calls to the
474 * user, based on the capabilities and/or current state of the device.
475 *
476 * If this method returns true, that means we should immediately reject the
477 * current incoming call, without even indicating to the user that an
478 * incoming call occurred.
479 *
480 * (We only reject incoming calls in a few cases, like during an OTASP call
481 * when we can't interrupt the user, or if the device hasn't completed the
482 * SetupWizard yet. We also don't allow incoming calls on non-voice-capable
483 * devices. But note that we *always* allow incoming calls while in ECM.)
484 *
485 * @return true if we're *not* allowed to present an incoming call to
486 * the user.
487 */
488 private boolean ignoreAllIncomingCalls(Phone phone) {
489 // Incoming calls are totally ignored on non-voice-capable devices.
490 if (!PhoneGlobals.sVoiceCapable) {
491 // ...but still log a warning, since we shouldn't have gotten this
492 // event in the first place! (Incoming calls *should* be blocked at
493 // the telephony layer on non-voice-capable capable devices.)
494 Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring...");
495 return true;
496 }
497
498 // In ECM (emergency callback mode), we ALWAYS allow incoming calls
499 // to get through to the user. (Note that ECM is applicable only to
500 // voice-capable CDMA devices).
501 if (PhoneUtils.isPhoneInEcm(phone)) {
502 if (DBG) log("Incoming call while in ECM: always allow...");
503 return false;
504 }
505
506 // Incoming calls are totally ignored if the device isn't provisioned yet.
507 boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(),
508 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
509 if (!provisioned) {
510 Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
511 return true;
512 }
513
514 // Incoming calls are totally ignored if an OTASP call is active.
515 if (TelephonyCapabilities.supportsOtasp(phone)) {
516 boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState
517 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
518 boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState
519 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG);
520 boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState;
521
522 if (spcState) {
523 Log.i(LOG_TAG, "Ignoring incoming call: OTA call is active");
524 return true;
525 } else if (activateState || dialogState) {
526 // We *are* allowed to receive incoming calls at this point.
527 // But clear out any residual OTASP UI first.
528 // TODO: It's an MVC violation to twiddle the OTA UI state here;
529 // we should instead provide a higher-level API via OtaUtils.
530 if (dialogState) mApplication.dismissOtaDialogs();
531 mApplication.clearOtaState();
532 mApplication.clearInCallScreenMode();
533 return false;
534 }
535 }
536
537 // Normal case: allow this call to be presented to the user.
538 return false;
539 }
540
541 /**
542 * Helper method to manage the start of incoming call queries
543 */
544 private void startIncomingCallQuery(Connection c) {
545 // TODO: cache the custom ringer object so that subsequent
546 // calls will not need to do this query work. We can keep
547 // the MRU ringtones in memory. We'll still need to hit
548 // the database to get the callerinfo to act as a key,
549 // but at least we can save the time required for the
550 // Media player setup. The only issue with this is that
551 // we may need to keep an eye on the resources the Media
552 // player uses to keep these ringtones around.
553
554 // make sure we're in a state where we can be ready to
555 // query a ringtone uri.
556 boolean shouldStartQuery = false;
557 synchronized (mCallerInfoQueryStateGuard) {
558 if (mCallerInfoQueryState == CALLERINFO_QUERY_READY) {
559 mCallerInfoQueryState = CALLERINFO_QUERYING;
560 shouldStartQuery = true;
561 }
562 }
563 if (shouldStartQuery) {
564 // Reset the ringtone to the default first.
565 mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
566
567 // query the callerinfo to try to get the ringer.
568 PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
569 mApplication, c, this, this);
570
571 // if this has already been queried then just ring, otherwise
572 // we wait for the alloted time before ringing.
573 if (cit.isFinal) {
574 if (VDBG) log("- CallerInfo already up to date, using available data");
575 onQueryComplete(0, this, cit.currentInfo);
576 } else {
577 if (VDBG) log("- Starting query, posting timeout message.");
578
579 // Phone number (via getAddress()) is stored in the message to remember which
580 // number is actually used for the look up.
581 sendMessageDelayed(
582 Message.obtain(this, RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT, c.getAddress()),
583 RINGTONE_QUERY_WAIT_TIME);
584 }
585 // The call to showIncomingCall() will happen after the
586 // queries are complete (or time out).
587 } else {
588 // This should never happen; its the case where an incoming call
589 // arrives at the same time that the query is still being run,
590 // and before the timeout window has closed.
591 EventLog.writeEvent(EventLogTags.PHONE_UI_MULTIPLE_QUERY);
592
593 // In this case, just log the request and ring.
594 if (VDBG) log("RINGING... (request to ring arrived while query is running)");
595 mRinger.ring();
596
597 // in this case, just fall through like before, and call
598 // showIncomingCall().
599 if (DBG) log("- showing incoming call (couldn't start query)...");
600 showIncomingCall();
601 }
602 }
603
604 /**
605 * Performs the final steps of the onNewRingingConnection sequence:
606 * starts the ringer, and brings up the "incoming call" UI.
607 *
608 * Normally, this is called when the CallerInfo query completes (see
609 * onQueryComplete()). In this case, onQueryComplete() has already
610 * configured the Ringer object to use the custom ringtone (if there
611 * is one) for this caller. So we just tell the Ringer to start, and
612 * proceed to the InCallScreen.
613 *
614 * But this method can *also* be called if the
615 * RINGTONE_QUERY_WAIT_TIME timeout expires, which means that the
616 * CallerInfo query is taking too long. In that case, we log a
617 * warning but otherwise we behave the same as in the normal case.
618 * (We still tell the Ringer to start, but it's going to use the
619 * default ringtone.)
620 */
621 private void onCustomRingQueryComplete() {
622 boolean isQueryExecutionTimeExpired = false;
623 synchronized (mCallerInfoQueryStateGuard) {
624 if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
625 mCallerInfoQueryState = CALLERINFO_QUERY_READY;
626 isQueryExecutionTimeExpired = true;
627 }
628 }
629 if (isQueryExecutionTimeExpired) {
630 // There may be a problem with the query here, since the
631 // default ringtone is playing instead of the custom one.
632 Log.w(LOG_TAG, "CallerInfo query took too long; falling back to default ringtone");
633 EventLog.writeEvent(EventLogTags.PHONE_UI_RINGER_QUERY_ELAPSED);
634 }
635
636 // Make sure we still have an incoming call!
637 //
638 // (It's possible for the incoming call to have been disconnected
639 // while we were running the query. In that case we better not
640 // start the ringer here, since there won't be any future
641 // DISCONNECT event to stop it!)
642 //
643 // Note we don't have to worry about the incoming call going away
644 // *after* this check but before we call mRinger.ring() below,
645 // since in that case we *will* still get a DISCONNECT message sent
646 // to our handler. (And we will correctly stop the ringer when we
647 // process that event.)
648 if (mCM.getState() != PhoneConstants.State.RINGING) {
649 Log.i(LOG_TAG, "onCustomRingQueryComplete: No incoming call! Bailing out...");
650 // Don't start the ringer *or* bring up the "incoming call" UI.
651 // Just bail out.
652 return;
653 }
654
655 // Ring, either with the queried ringtone or default one.
656 if (VDBG) log("RINGING... (onCustomRingQueryComplete)");
657 mRinger.ring();
658
659 // ...and display the incoming call to the user:
660 if (DBG) log("- showing incoming call (custom ring query complete)...");
661 showIncomingCall();
662 }
663
664 private void onUnknownConnectionAppeared(AsyncResult r) {
665 PhoneConstants.State state = mCM.getState();
666
667 if (state == PhoneConstants.State.OFFHOOK) {
668 // basically do onPhoneStateChanged + display the incoming call UI
669 onPhoneStateChanged(r);
670 if (DBG) log("- showing incoming call (unknown connection appeared)...");
671 showIncomingCall();
672 }
673 }
674
675 /**
676 * Informs the user about a new incoming call.
677 *
678 * In most cases this means "bring up the full-screen incoming call
679 * UI". However, if an immersive activity is running, the system
680 * NotificationManager will instead pop up a small notification window
681 * on top of the activity.
682 *
683 * Watch out: be sure to call this method only once per incoming call,
684 * or otherwise we may end up launching the InCallScreen multiple
685 * times (which can lead to slow responsiveness and/or visible
686 * glitches.)
687 *
688 * Note this method handles only the onscreen UI for incoming calls;
689 * the ringer and/or vibrator are started separately (see the various
690 * calls to Ringer.ring() in this class.)
691 *
692 * @see NotificationMgr#updateNotificationAndLaunchIncomingCallUi()
693 */
694 private void showIncomingCall() {
695 log("showIncomingCall()... phone state = " + mCM.getState());
696
697 // Before bringing up the "incoming call" UI, force any system
698 // dialogs (like "recent tasks" or the power dialog) to close first.
699 try {
700 ActivityManagerNative.getDefault().closeSystemDialogs("call");
701 } catch (RemoteException e) {
702 }
703
704 // Go directly to the in-call screen.
705 // (No need to do anything special if we're already on the in-call
706 // screen; it'll notice the phone state change and update itself.)
707 mApplication.requestWakeState(PhoneGlobals.WakeState.FULL);
708
709 // Post the "incoming call" notification *and* include the
710 // fullScreenIntent that'll launch the incoming-call UI.
711 // (This will usually take us straight to the incoming call
712 // screen, but if an immersive activity is running it'll just
713 // appear as a notification.)
714 if (DBG) log("- updating notification from showIncomingCall()...");
715 mApplication.notificationMgr.updateNotificationAndLaunchIncomingCallUi();
716 }
717
718 /**
719 * Updates the phone UI in response to phone state changes.
720 *
721 * Watch out: certain state changes are actually handled by their own
722 * specific methods:
723 * - see onNewRingingConnection() for new incoming calls
724 * - see onDisconnect() for calls being hung up or disconnected
725 */
726 private void onPhoneStateChanged(AsyncResult r) {
727 PhoneConstants.State state = mCM.getState();
728 if (VDBG) log("onPhoneStateChanged: state = " + state);
729
730 // Turn status bar notifications on or off depending upon the state
731 // of the phone. Notification Alerts (audible or vibrating) should
732 // be on if and only if the phone is IDLE.
733 mApplication.notificationMgr.statusBarHelper
734 .enableNotificationAlerts(state == PhoneConstants.State.IDLE);
735
736 Phone fgPhone = mCM.getFgPhone();
737 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
738 if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE)
739 && ((mPreviousCdmaCallState == Call.State.DIALING)
740 || (mPreviousCdmaCallState == Call.State.ALERTING))) {
741 if (mIsCdmaRedialCall) {
742 int toneToPlay = InCallTonePlayer.TONE_REDIAL;
743 new InCallTonePlayer(toneToPlay).start();
744 }
745 // Stop any signal info tone when call moves to ACTIVE state
746 stopSignalInfoTone();
747 }
748 mPreviousCdmaCallState = fgPhone.getForegroundCall().getState();
749 }
750
751 // Have the PhoneApp recompute its mShowBluetoothIndication
752 // flag based on the (new) telephony state.
753 // There's no need to force a UI update since we update the
754 // in-call notification ourselves (below), and the InCallScreen
755 // listens for phone state changes itself.
756 mApplication.updateBluetoothIndication(false);
757
758
759 // Update the phone state and other sensor/lock.
760 mApplication.updatePhoneState(state);
761
762 if (state == PhoneConstants.State.OFFHOOK) {
763 // stop call waiting tone if needed when answering
764 if (mCallWaitingTonePlayer != null) {
765 mCallWaitingTonePlayer.stopTone();
766 mCallWaitingTonePlayer = null;
767 }
768
769 if (VDBG) log("onPhoneStateChanged: OFF HOOK");
770 // make sure audio is in in-call mode now
771 PhoneUtils.setAudioMode(mCM);
772
773 // if the call screen is showing, let it handle the event,
774 // otherwise handle it here.
775 if (!mApplication.isShowingCallScreen()) {
776 mApplication.requestWakeState(PhoneGlobals.WakeState.SLEEP);
777 }
778
779 // Since we're now in-call, the Ringer should definitely *not*
780 // be ringing any more. (This is just a sanity-check; we
781 // already stopped the ringer explicitly back in
782 // PhoneUtils.answerCall(), before the call to phone.acceptCall().)
783 // TODO: Confirm that this call really *is* unnecessary, and if so,
784 // remove it!
785 if (DBG) log("stopRing()... (OFFHOOK state)");
786 mRinger.stopRing();
787
788 // Post a request to update the "in-call" status bar icon.
789 //
790 // We don't call NotificationMgr.updateInCallNotification()
791 // directly here, for two reasons:
792 // (1) a single phone state change might actually trigger multiple
793 // onPhoneStateChanged() callbacks, so this prevents redundant
794 // updates of the notification.
795 // (2) we suppress the status bar icon while the in-call UI is
796 // visible (see updateInCallNotification()). But when launching
797 // an outgoing call the phone actually goes OFFHOOK slightly
798 // *before* the InCallScreen comes up, so the delay here avoids a
799 // brief flicker of the icon at that point.
800
801 if (DBG) log("- posting UPDATE_IN_CALL_NOTIFICATION request...");
802 // Remove any previous requests in the queue
803 removeMessages(UPDATE_IN_CALL_NOTIFICATION);
804 final int IN_CALL_NOTIFICATION_UPDATE_DELAY = 1000; // msec
805 sendEmptyMessageDelayed(UPDATE_IN_CALL_NOTIFICATION,
806 IN_CALL_NOTIFICATION_UPDATE_DELAY);
807 }
808
809 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
810 Connection c = fgPhone.getForegroundCall().getLatestConnection();
811 if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
812 mApplication))) {
813 if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
814 Call.State callState = fgPhone.getForegroundCall().getState();
815 if (mEmergencyTonePlayerVibrator == null) {
816 mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
817 }
818
819 if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
820 mIsEmergencyToneOn = Settings.Global.getInt(
821 mApplication.getContentResolver(),
822 Settings.Global.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
823 if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
824 mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
825 if (mEmergencyTonePlayerVibrator != null) {
826 mEmergencyTonePlayerVibrator.start();
827 }
828 }
829 } else if (callState == Call.State.ACTIVE) {
830 if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) {
831 if (mEmergencyTonePlayerVibrator != null) {
832 mEmergencyTonePlayerVibrator.stop();
833 }
834 }
835 }
836 }
837 }
838
839 if ((fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
840 || (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP)) {
841 Call.State callState = mCM.getActiveFgCallState();
842 if (!callState.isDialing()) {
843 // If call get activated or disconnected before the ringback
844 // tone stops, we have to stop it to prevent disturbing.
845 if (mInCallRingbackTonePlayer != null) {
846 mInCallRingbackTonePlayer.stopTone();
847 mInCallRingbackTonePlayer = null;
848 }
849 }
850 }
851 }
852
853 void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
854 if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
855
856 // Clear ringback tone player
857 mInCallRingbackTonePlayer = null;
858
859 // Clear call waiting tone player
860 mCallWaitingTonePlayer = null;
861
862 // Instantiate mSignalInfoToneGenerator
863 createSignalInfoToneGenerator();
864 }
865
866 /**
867 * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
868 * refreshes the CallCard data when it called. If called with this
869 * class itself, it is assumed that we have been waiting for the ringtone
870 * and direct to voicemail settings to update.
871 */
872 @Override
873 public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
874 if (cookie instanceof Long) {
875 if (VDBG) log("CallerInfo query complete, posting missed call notification");
876
877 mApplication.notificationMgr.notifyMissedCall(ci.name, ci.phoneNumber,
878 ci.phoneLabel, ci.cachedPhoto, ci.cachedPhotoIcon,
879 ((Long) cookie).longValue());
880 } else if (cookie instanceof CallNotifier) {
881 if (VDBG) log("CallerInfo query complete (for CallNotifier), "
882 + "updating state for incoming call..");
883
884 // get rid of the timeout messages
885 removeMessages(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT);
886
887 boolean isQueryExecutionTimeOK = false;
888 synchronized (mCallerInfoQueryStateGuard) {
889 if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
890 mCallerInfoQueryState = CALLERINFO_QUERY_READY;
891 isQueryExecutionTimeOK = true;
892 }
893 }
894 //if we're in the right state
895 if (isQueryExecutionTimeOK) {
896
897 // send directly to voicemail.
898 if (ci.shouldSendToVoicemail) {
899 if (DBG) log("send to voicemail flag detected. hanging up.");
900 PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
901 return;
902 }
903
904 // set the ringtone uri to prepare for the ring.
905 if (ci.contactRingtoneUri != null) {
906 if (DBG) log("custom ringtone found, setting up ringer.");
907 Ringer r = ((CallNotifier) cookie).mRinger;
908 r.setCustomRingtoneUri(ci.contactRingtoneUri);
909 }
910 // ring, and other post-ring actions.
911 onCustomRingQueryComplete();
912 }
913 }
914 }
915
916 /**
917 * Called when asynchronous CallerInfo query is taking too long (more than
918 * {@link #RINGTONE_QUERY_WAIT_TIME} msec), but we cannot wait any more.
919 *
920 * This looks up in-memory fallback cache and use it when available. If not, it just calls
921 * {@link #onCustomRingQueryComplete()} with default ringtone ("Send to voicemail" flag will
922 * be just ignored).
923 *
924 * @param number The phone number used for the async query. This method will take care of
925 * formatting or normalization of the number.
926 */
927 private void onCustomRingtoneQueryTimeout(String number) {
928 // First of all, this case itself should be rare enough, though we cannot avoid it in
929 // some situations (e.g. IPC is slow due to system overload, database is in sync, etc.)
930 Log.w(LOG_TAG, "CallerInfo query took too long; look up local fallback cache.");
931
932 // This method is intentionally verbose for now to detect possible bad side-effect for it.
933 // TODO: Remove the verbose log when it looks stable and reliable enough.
934
935 final CallerInfoCache.CacheEntry entry =
936 mApplication.callerInfoCache.getCacheEntry(number);
937 if (entry != null) {
938 if (entry.sendToVoicemail) {
939 log("send to voicemail flag detected (in fallback cache). hanging up.");
940 PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
941 return;
942 }
943
944 if (entry.customRingtone != null) {
945 log("custom ringtone found (in fallback cache), setting up ringer: "
946 + entry.customRingtone);
947 this.mRinger.setCustomRingtoneUri(Uri.parse(entry.customRingtone));
948 }
949 } else {
950 // In this case we call onCustomRingQueryComplete(), just
951 // like if the query had completed normally. (But we're
952 // going to get the default ringtone, since we never got
953 // the chance to call Ringer.setCustomRingtoneUri()).
954 log("Failed to find fallback cache. Use default ringer tone.");
955 }
956
957 onCustomRingQueryComplete();
958 }
959
960 private void onDisconnect(AsyncResult r) {
961 if (VDBG) log("onDisconnect()... CallManager state: " + mCM.getState());
962
963 mVoicePrivacyState = false;
964 Connection c = (Connection) r.result;
965 if (c != null) {
966 log("onDisconnect: cause = " + c.getDisconnectCause()
967 + ", incoming = " + c.isIncoming()
968 + ", date = " + c.getCreateTime());
969 } else {
970 Log.w(LOG_TAG, "onDisconnect: null connection");
971 }
972
973 int autoretrySetting = 0;
974 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
975 autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
976 getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
977 }
978
979 // Stop any signalInfo tone being played when a call gets ended
980 stopSignalInfoTone();
981
982 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
983 // Resetting the CdmaPhoneCallState members
984 mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
985
986 // Remove Call waiting timers
987 removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
988 removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
989 }
990
991 // Stop the ringer if it was ringing (for an incoming call that
992 // either disconnected by itself, or was rejected by the user.)
993 //
994 // TODO: We technically *shouldn't* stop the ringer if the
995 // foreground or background call disconnects while an incoming call
996 // is still ringing, but that's a really rare corner case.
997 // It's safest to just unconditionally stop the ringer here.
998
999 // CDMA: For Call collision cases i.e. when the user makes an out going call
1000 // and at the same time receives an Incoming Call, the Incoming Call is given
1001 // higher preference. At this time framework sends a disconnect for the Out going
1002 // call connection hence we should *not* be stopping the ringer being played for
1003 // the Incoming Call
1004 Call ringingCall = mCM.getFirstActiveRingingCall();
1005 if (ringingCall.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
1006 if (PhoneUtils.isRealIncomingCall(ringingCall.getState())) {
1007 // Also we need to take off the "In Call" icon from the Notification
1008 // area as the Out going Call never got connected
1009 if (DBG) log("cancelCallInProgressNotifications()... (onDisconnect)");
1010 mApplication.notificationMgr.cancelCallInProgressNotifications();
1011 } else {
1012 if (DBG) log("stopRing()... (onDisconnect)");
1013 mRinger.stopRing();
1014 }
1015 } else { // GSM
1016 if (DBG) log("stopRing()... (onDisconnect)");
1017 mRinger.stopRing();
1018 }
1019
1020 // stop call waiting tone if needed when disconnecting
1021 if (mCallWaitingTonePlayer != null) {
1022 mCallWaitingTonePlayer.stopTone();
1023 mCallWaitingTonePlayer = null;
1024 }
1025
1026 // If this is the end of an OTASP call, pass it on to the PhoneApp.
1027 if (c != null && TelephonyCapabilities.supportsOtasp(c.getCall().getPhone())) {
1028 final String number = c.getAddress();
1029 if (c.getCall().getPhone().isOtaSpNumber(number)) {
1030 if (DBG) log("onDisconnect: this was an OTASP call!");
1031 mApplication.handleOtaspDisconnect();
1032 }
1033 }
1034
1035 // Check for the various tones we might need to play (thru the
1036 // earpiece) after a call disconnects.
1037 int toneToPlay = InCallTonePlayer.TONE_NONE;
1038
1039 // The "Busy" or "Congestion" tone is the highest priority:
1040 if (c != null) {
1041 Connection.DisconnectCause cause = c.getDisconnectCause();
1042 if (cause == Connection.DisconnectCause.BUSY) {
1043 if (DBG) log("- need to play BUSY tone!");
1044 toneToPlay = InCallTonePlayer.TONE_BUSY;
1045 } else if (cause == Connection.DisconnectCause.CONGESTION) {
1046 if (DBG) log("- need to play CONGESTION tone!");
1047 toneToPlay = InCallTonePlayer.TONE_CONGESTION;
1048 } else if (((cause == Connection.DisconnectCause.NORMAL)
1049 || (cause == Connection.DisconnectCause.LOCAL))
1050 && (mApplication.isOtaCallInActiveState())) {
1051 if (DBG) log("- need to play OTA_CALL_END tone!");
1052 toneToPlay = InCallTonePlayer.TONE_OTA_CALL_END;
1053 } else if (cause == Connection.DisconnectCause.CDMA_REORDER) {
1054 if (DBG) log("- need to play CDMA_REORDER tone!");
1055 toneToPlay = InCallTonePlayer.TONE_REORDER;
1056 } else if (cause == Connection.DisconnectCause.CDMA_INTERCEPT) {
1057 if (DBG) log("- need to play CDMA_INTERCEPT tone!");
1058 toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
1059 } else if (cause == Connection.DisconnectCause.CDMA_DROP) {
1060 if (DBG) log("- need to play CDMA_DROP tone!");
1061 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
1062 } else if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
1063 if (DBG) log("- need to play OUT OF SERVICE tone!");
1064 toneToPlay = InCallTonePlayer.TONE_OUT_OF_SERVICE;
1065 } else if (cause == Connection.DisconnectCause.UNOBTAINABLE_NUMBER) {
1066 if (DBG) log("- need to play TONE_UNOBTAINABLE_NUMBER tone!");
1067 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
1068 } else if (cause == Connection.DisconnectCause.ERROR_UNSPECIFIED) {
1069 if (DBG) log("- DisconnectCause is ERROR_UNSPECIFIED: play TONE_CALL_ENDED!");
1070 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
1071 }
1072 }
1073
1074 // If we don't need to play BUSY or CONGESTION, then play the
1075 // "call ended" tone if this was a "regular disconnect" (i.e. a
1076 // normal call where one end or the other hung up) *and* this
1077 // disconnect event caused the phone to become idle. (In other
1078 // words, we *don't* play the sound if one call hangs up but
1079 // there's still an active call on the other line.)
1080 // TODO: We may eventually want to disable this via a preference.
1081 if ((toneToPlay == InCallTonePlayer.TONE_NONE)
1082 && (mCM.getState() == PhoneConstants.State.IDLE)
1083 && (c != null)) {
1084 Connection.DisconnectCause cause = c.getDisconnectCause();
1085 if ((cause == Connection.DisconnectCause.NORMAL) // remote hangup
1086 || (cause == Connection.DisconnectCause.LOCAL)) { // local hangup
1087 if (VDBG) log("- need to play CALL_ENDED tone!");
1088 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
1089 mIsCdmaRedialCall = false;
1090 }
1091 }
1092
1093 // All phone calls are disconnected.
1094 if (mCM.getState() == PhoneConstants.State.IDLE) {
1095 // Don't reset the audio mode or bluetooth/speakerphone state
1096 // if we still need to let the user hear a tone through the earpiece.
1097 if (toneToPlay == InCallTonePlayer.TONE_NONE) {
1098 resetAudioStateAfterDisconnect();
1099 }
1100
1101 mApplication.notificationMgr.cancelCallInProgressNotifications();
1102 }
1103
1104 if (c != null) {
1105 mCallLogger.logCall(c);
1106
1107 final String number = c.getAddress();
1108 final Phone phone = c.getCall().getPhone();
1109 final boolean isEmergencyNumber =
1110 PhoneNumberUtils.isLocalEmergencyNumber(number, mApplication);
1111
1112 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
1113 if ((isEmergencyNumber)
1114 && (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF)) {
1115 if (mEmergencyTonePlayerVibrator != null) {
1116 mEmergencyTonePlayerVibrator.stop();
1117 }
1118 }
1119 }
1120
1121 final long date = c.getCreateTime();
1122 final Connection.DisconnectCause cause = c.getDisconnectCause();
1123 final boolean missedCall = c.isIncoming() &&
1124 (cause == Connection.DisconnectCause.INCOMING_MISSED);
1125 if (missedCall) {
1126 // Show the "Missed call" notification.
1127 // (Note we *don't* do this if this was an incoming call that
1128 // the user deliberately rejected.)
1129 showMissedCallNotification(c, date);
1130 }
1131
1132 // Possibly play a "post-disconnect tone" thru the earpiece.
1133 // We do this here, rather than from the InCallScreen
1134 // activity, since we need to do this even if you're not in
1135 // the Phone UI at the moment the connection ends.
1136 if (toneToPlay != InCallTonePlayer.TONE_NONE) {
1137 if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
1138 new InCallTonePlayer(toneToPlay).start();
1139
1140 // TODO: alternatively, we could start an InCallTonePlayer
1141 // here with an "unlimited" tone length,
1142 // and manually stop it later when this connection truly goes
1143 // away. (The real connection over the network was closed as soon
1144 // as we got the BUSY message. But our telephony layer keeps the
1145 // connection open for a few extra seconds so we can show the
1146 // "busy" indication to the user. We could stop the busy tone
1147 // when *that* connection's "disconnect" event comes in.)
1148 }
1149
1150 if (((mPreviousCdmaCallState == Call.State.DIALING)
1151 || (mPreviousCdmaCallState == Call.State.ALERTING))
1152 && (!isEmergencyNumber)
1153 && (cause != Connection.DisconnectCause.INCOMING_MISSED )
1154 && (cause != Connection.DisconnectCause.NORMAL)
1155 && (cause != Connection.DisconnectCause.LOCAL)
1156 && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1157 if (!mIsCdmaRedialCall) {
1158 if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
1159 // TODO: (Moto): The contact reference data may need to be stored and use
1160 // here when redialing a call. For now, pass in NULL as the URI parameter.
1161 PhoneUtils.placeCall(mApplication, phone, number, null, false, null);
1162 mIsCdmaRedialCall = true;
1163 } else {
1164 mIsCdmaRedialCall = false;
1165 }
1166 } else {
1167 mIsCdmaRedialCall = false;
1168 }
1169 }
1170 }
1171 }
1172
1173 /**
1174 * Resets the audio mode and speaker state when a call ends.
1175 */
1176 private void resetAudioStateAfterDisconnect() {
1177 if (VDBG) log("resetAudioStateAfterDisconnect()...");
1178
1179 if (mBluetoothHeadset != null) {
1180 mBluetoothHeadset.disconnectAudio();
1181 }
1182
1183 // call turnOnSpeaker() with state=false and store=true even if speaker
1184 // is already off to reset user requested speaker state.
1185 PhoneUtils.turnOnSpeaker(mApplication, false, true);
1186
1187 PhoneUtils.setAudioMode(mCM);
1188 }
1189
1190 private void onMwiChanged(boolean visible) {
1191 if (VDBG) log("onMwiChanged(): " + visible);
1192
1193 // "Voicemail" is meaningless on non-voice-capable devices,
1194 // so ignore MWI events.
1195 if (!PhoneGlobals.sVoiceCapable) {
1196 // ...but still log a warning, since we shouldn't have gotten this
1197 // event in the first place!
1198 // (PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR events
1199 // *should* be blocked at the telephony layer on non-voice-capable
1200 // capable devices.)
1201 Log.w(LOG_TAG, "Got onMwiChanged() on non-voice-capable device! Ignoring...");
1202 return;
1203 }
1204
1205 mApplication.notificationMgr.updateMwi(visible);
1206 }
1207
1208 /**
1209 * Posts a delayed PHONE_MWI_CHANGED event, to schedule a "retry" for a
1210 * failed NotificationMgr.updateMwi() call.
1211 */
1212 /* package */ void sendMwiChangedDelayed(long delayMillis) {
1213 Message message = Message.obtain(this, PHONE_MWI_CHANGED);
1214 sendMessageDelayed(message, delayMillis);
1215 }
1216
1217 private void onCfiChanged(boolean visible) {
1218 if (VDBG) log("onCfiChanged(): " + visible);
1219 mApplication.notificationMgr.updateCfi(visible);
1220 }
1221
1222 /**
1223 * Indicates whether or not this ringer is ringing.
1224 */
1225 boolean isRinging() {
1226 return mRinger.isRinging();
1227 }
1228
1229 /**
1230 * Stops the current ring, and tells the notifier that future
1231 * ring requests should be ignored.
1232 */
1233 void silenceRinger() {
1234 mSilentRingerRequested = true;
1235 if (DBG) log("stopRing()... (silenceRinger)");
1236 mRinger.stopRing();
1237 }
1238
1239 /**
1240 * Restarts the ringer after having previously silenced it.
1241 *
1242 * (This is a no-op if the ringer is actually still ringing, or if the
1243 * incoming ringing call no longer exists.)
1244 */
1245 /* package */ void restartRinger() {
1246 if (DBG) log("restartRinger()...");
1247 // Already ringing or Silent requested; no need to restart.
1248 if (isRinging() || mSilentRingerRequested) return;
1249
1250 final Call ringingCall = mCM.getFirstActiveRingingCall();
1251 // Don't check ringingCall.isRinging() here, since that'll be true
1252 // for the WAITING state also. We only allow the ringer for
1253 // regular INCOMING calls.
1254 if (DBG) log("- ringingCall state: " + ringingCall.getState());
1255 if (ringingCall.getState() == Call.State.INCOMING) {
1256 mRinger.ring();
1257 }
1258 }
1259
1260 /**
1261 * Helper class to play tones through the earpiece (or speaker / BT)
1262 * during a call, using the ToneGenerator.
1263 *
1264 * To use, just instantiate a new InCallTonePlayer
1265 * (passing in the TONE_* constant for the tone you want)
1266 * and start() it.
1267 *
1268 * When we're done playing the tone, if the phone is idle at that
1269 * point, we'll reset the audio routing and speaker state.
1270 * (That means that for tones that get played *after* a call
1271 * disconnects, like "busy" or "congestion" or "call ended", you
1272 * should NOT call resetAudioStateAfterDisconnect() yourself.
1273 * Instead, just start the InCallTonePlayer, which will automatically
1274 * defer the resetAudioStateAfterDisconnect() call until the tone
1275 * finishes playing.)
1276 */
1277 private class InCallTonePlayer extends Thread {
1278 private int mToneId;
1279 private int mState;
1280 // The possible tones we can play.
1281 public static final int TONE_NONE = 0;
1282 public static final int TONE_CALL_WAITING = 1;
1283 public static final int TONE_BUSY = 2;
1284 public static final int TONE_CONGESTION = 3;
1285 public static final int TONE_CALL_ENDED = 4;
1286 public static final int TONE_VOICE_PRIVACY = 5;
1287 public static final int TONE_REORDER = 6;
1288 public static final int TONE_INTERCEPT = 7;
1289 public static final int TONE_CDMA_DROP = 8;
1290 public static final int TONE_OUT_OF_SERVICE = 9;
1291 public static final int TONE_REDIAL = 10;
1292 public static final int TONE_OTA_CALL_END = 11;
1293 public static final int TONE_RING_BACK = 12;
1294 public static final int TONE_UNOBTAINABLE_NUMBER = 13;
1295
1296 // The tone volume relative to other sounds in the stream
1297 static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
1298 static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
1299 static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
1300
1301 // Buffer time (in msec) to add on to tone timeout value.
1302 // Needed mainly when the timeout value for a tone is the
1303 // exact duration of the tone itself.
1304 static final int TONE_TIMEOUT_BUFFER = 20;
1305
1306 // The tone state
1307 static final int TONE_OFF = 0;
1308 static final int TONE_ON = 1;
1309 static final int TONE_STOPPED = 2;
1310
1311 InCallTonePlayer(int toneId) {
1312 super();
1313 mToneId = toneId;
1314 mState = TONE_OFF;
1315 }
1316
1317 @Override
1318 public void run() {
1319 log("InCallTonePlayer.run(toneId = " + mToneId + ")...");
1320
1321 int toneType = 0; // passed to ToneGenerator.startTone()
1322 int toneVolume; // passed to the ToneGenerator constructor
1323 int toneLengthMillis;
1324 int phoneType = mCM.getFgPhone().getPhoneType();
1325
1326 switch (mToneId) {
1327 case TONE_CALL_WAITING:
1328 toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
1329 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1330 // Call waiting tone is stopped by stopTone() method
1331 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
1332 break;
1333 case TONE_BUSY:
1334 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1335 toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
1336 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1337 toneLengthMillis = 1000;
1338 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1339 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
1340 toneType = ToneGenerator.TONE_SUP_BUSY;
1341 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1342 toneLengthMillis = 4000;
1343 } else {
1344 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1345 }
1346 break;
1347 case TONE_CONGESTION:
1348 toneType = ToneGenerator.TONE_SUP_CONGESTION;
1349 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1350 toneLengthMillis = 4000;
1351 break;
1352
1353 case TONE_CALL_ENDED:
1354 toneType = ToneGenerator.TONE_PROP_PROMPT;
1355 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1356 toneLengthMillis = 200;
1357 break;
1358 case TONE_OTA_CALL_END:
1359 if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone ==
1360 OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) {
1361 toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD;
1362 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1363 toneLengthMillis = 750;
1364 } else {
1365 toneType = ToneGenerator.TONE_PROP_PROMPT;
1366 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1367 toneLengthMillis = 200;
1368 }
1369 break;
1370 case TONE_VOICE_PRIVACY:
1371 toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
1372 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1373 toneLengthMillis = 5000;
1374 break;
1375 case TONE_REORDER:
1376 toneType = ToneGenerator.TONE_CDMA_REORDER;
1377 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1378 toneLengthMillis = 4000;
1379 break;
1380 case TONE_INTERCEPT:
1381 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
1382 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1383 toneLengthMillis = 500;
1384 break;
1385 case TONE_CDMA_DROP:
1386 case TONE_OUT_OF_SERVICE:
1387 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
1388 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1389 toneLengthMillis = 375;
1390 break;
1391 case TONE_REDIAL:
1392 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
1393 toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
1394 toneLengthMillis = 5000;
1395 break;
1396 case TONE_RING_BACK:
1397 toneType = ToneGenerator.TONE_SUP_RINGTONE;
1398 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1399 // Call ring back tone is stopped by stopTone() method
1400 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
1401 break;
1402 case TONE_UNOBTAINABLE_NUMBER:
1403 toneType = ToneGenerator.TONE_SUP_ERROR;
1404 toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
1405 toneLengthMillis = 4000;
1406 break;
1407 default:
1408 throw new IllegalArgumentException("Bad toneId: " + mToneId);
1409 }
1410
1411 // If the mToneGenerator creation fails, just continue without it. It is
1412 // a local audio signal, and is not as important.
1413 ToneGenerator toneGenerator;
1414 try {
1415 int stream;
1416 if (mBluetoothHeadset != null) {
1417 stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
1418 AudioManager.STREAM_VOICE_CALL;
1419 } else {
1420 stream = AudioManager.STREAM_VOICE_CALL;
1421 }
1422 toneGenerator = new ToneGenerator(stream, toneVolume);
1423 // if (DBG) log("- created toneGenerator: " + toneGenerator);
1424 } catch (RuntimeException e) {
1425 Log.w(LOG_TAG,
1426 "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
1427 toneGenerator = null;
1428 }
1429
1430 // Using the ToneGenerator (with the CALL_WAITING / BUSY /
1431 // CONGESTION tones at least), the ToneGenerator itself knows
1432 // the right pattern of tones to play; we do NOT need to
1433 // manually start/stop each individual tone, or manually
1434 // insert the correct delay between tones. (We just start it
1435 // and let it run for however long we want the tone pattern to
1436 // continue.)
1437 //
1438 // TODO: When we stop the ToneGenerator in the middle of a
1439 // "tone pattern", it sounds bad if we cut if off while the
1440 // tone is actually playing. Consider adding API to the
1441 // ToneGenerator to say "stop at the next silent part of the
1442 // pattern", or simply "play the pattern N times and then
1443 // stop."
1444 boolean needToStopTone = true;
1445 boolean okToPlayTone = false;
1446
1447 if (toneGenerator != null) {
1448 int ringerMode = mAudioManager.getRingerMode();
1449 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1450 if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) {
1451 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
1452 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
1453 if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType);
1454 okToPlayTone = true;
1455 needToStopTone = false;
1456 }
1457 } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) ||
1458 (toneType == ToneGenerator.TONE_CDMA_REORDER) ||
1459 (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) ||
1460 (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) ||
1461 (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) {
1462 if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
1463 if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType);
1464 okToPlayTone = true;
1465 needToStopTone = false;
1466 }
1467 } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) ||
1468 (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) {
1469 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
1470 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
1471 if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType);
1472 okToPlayTone = true;
1473 needToStopTone = false;
1474 }
1475 } else { // For the rest of the tones, always OK to play.
1476 okToPlayTone = true;
1477 }
1478 } else { // Not "CDMA"
1479 okToPlayTone = true;
1480 }
1481
1482 synchronized (this) {
1483 if (okToPlayTone && mState != TONE_STOPPED) {
1484 mState = TONE_ON;
1485 toneGenerator.startTone(toneType);
1486 try {
1487 wait(toneLengthMillis + TONE_TIMEOUT_BUFFER);
1488 } catch (InterruptedException e) {
1489 Log.w(LOG_TAG,
1490 "InCallTonePlayer stopped: " + e);
1491 }
1492 if (needToStopTone) {
1493 toneGenerator.stopTone();
1494 }
1495 }
1496 // if (DBG) log("- InCallTonePlayer: done playing.");
1497 toneGenerator.release();
1498 mState = TONE_OFF;
1499 }
1500 }
1501
1502 // Finally, do the same cleanup we otherwise would have done
1503 // in onDisconnect().
1504 //
1505 // (But watch out: do NOT do this if the phone is in use,
1506 // since some of our tones get played *during* a call (like
1507 // CALL_WAITING) and we definitely *don't*
1508 // want to reset the audio mode / speaker / bluetooth after
1509 // playing those!
1510 // This call is really here for use with tones that get played
1511 // *after* a call disconnects, like "busy" or "congestion" or
1512 // "call ended", where the phone has already become idle but
1513 // we need to defer the resetAudioStateAfterDisconnect() call
1514 // till the tone finishes playing.)
1515 if (mCM.getState() == PhoneConstants.State.IDLE) {
1516 resetAudioStateAfterDisconnect();
1517 }
1518 }
1519
1520 public void stopTone() {
1521 synchronized (this) {
1522 if (mState == TONE_ON) {
1523 notify();
1524 }
1525 mState = TONE_STOPPED;
1526 }
1527 }
1528 }
1529
1530 /**
1531 * Displays a notification when the phone receives a DisplayInfo record.
1532 */
1533 private void onDisplayInfo(AsyncResult r) {
1534 // Extract the DisplayInfo String from the message
1535 CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
1536
1537 if (displayInfoRec != null) {
1538 String displayInfo = displayInfoRec.alpha;
1539 if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
1540 CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
1541
1542 // start a 2 second timer
1543 sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
1544 DISPLAYINFO_NOTIFICATION_TIME);
1545 }
1546 }
1547
1548 /**
1549 * Helper class to play SignalInfo tones using the ToneGenerator.
1550 *
1551 * To use, just instantiate a new SignalInfoTonePlayer
1552 * (passing in the ToneID constant for the tone you want)
1553 * and start() it.
1554 */
1555 private class SignalInfoTonePlayer extends Thread {
1556 private int mToneId;
1557
1558 SignalInfoTonePlayer(int toneId) {
1559 super();
1560 mToneId = toneId;
1561 }
1562
1563 @Override
1564 public void run() {
1565 log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
1566
1567 if (mSignalInfoToneGenerator != null) {
1568 //First stop any ongoing SignalInfo tone
1569 mSignalInfoToneGenerator.stopTone();
1570
1571 //Start playing the new tone if its a valid tone
1572 mSignalInfoToneGenerator.startTone(mToneId);
1573 }
1574 }
1575 }
1576
1577 /**
1578 * Plays a tone when the phone receives a SignalInfo record.
1579 */
1580 private void onSignalInfo(AsyncResult r) {
1581 // Signal Info are totally ignored on non-voice-capable devices.
1582 if (!PhoneGlobals.sVoiceCapable) {
1583 Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring...");
1584 return;
1585 }
1586
1587 if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) {
1588 // Do not start any new SignalInfo tone when Call state is INCOMING
1589 // and stop any previous SignalInfo tone which is being played
1590 stopSignalInfoTone();
1591 } else {
1592 // Extract the SignalInfo String from the message
1593 CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
1594 // Only proceed if a Signal info is present.
1595 if (signalInfoRec != null) {
1596 boolean isPresent = signalInfoRec.isPresent;
1597 if (DBG) log("onSignalInfo: isPresent=" + isPresent);
1598 if (isPresent) {// if tone is valid
1599 int uSignalType = signalInfoRec.signalType;
1600 int uAlertPitch = signalInfoRec.alertPitch;
1601 int uSignal = signalInfoRec.signal;
1602
1603 if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
1604 uAlertPitch + ", uSignal=" + uSignal);
1605 //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1606 int toneID = SignalToneUtil.getAudioToneFromSignalInfo
1607 (uSignalType, uAlertPitch, uSignal);
1608
1609 //Create the SignalInfo tone player and pass the ToneID
1610 new SignalInfoTonePlayer(toneID).start();
1611 }
1612 }
1613 }
1614 }
1615
1616 /**
1617 * Stops a SignalInfo tone in the following condition
1618 * 1 - On receiving a New Ringing Call
1619 * 2 - On disconnecting a call
1620 * 3 - On answering a Call Waiting Call
1621 */
1622 /* package */ void stopSignalInfoTone() {
1623 if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player");
1624 new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
1625 }
1626
1627 /**
1628 * Plays a Call waiting tone if it is present in the second incoming call.
1629 */
1630 private void onCdmaCallWaiting(AsyncResult r) {
1631 // Remove any previous Call waiting timers in the queue
1632 removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
1633 removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
1634
1635 // Set the Phone Call State to SINGLE_ACTIVE as there is only one connection
1636 // else we would not have received Call waiting
1637 mApplication.cdmaPhoneCallState.setCurrentCallState(
1638 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
1639
1640 // Display the incoming call to the user if the InCallScreen isn't
1641 // already in the foreground.
1642 if (!mApplication.isShowingCallScreen()) {
1643 if (DBG) log("- showing incoming call (CDMA call waiting)...");
1644 showIncomingCall();
1645 }
1646
1647 // Start timer for CW display
1648 mCallWaitingTimeOut = false;
1649 sendEmptyMessageDelayed(CALLWAITING_CALLERINFO_DISPLAY_DONE,
1650 CALLWAITING_CALLERINFO_DISPLAY_TIME);
1651
1652 // Set the mAddCallMenuStateAfterCW state to false
1653 mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(false);
1654
1655 // Start the timer for disabling "Add Call" menu option
1656 sendEmptyMessageDelayed(CALLWAITING_ADDCALL_DISABLE_TIMEOUT,
1657 CALLWAITING_ADDCALL_DISABLE_TIME);
1658
1659 // Extract the Call waiting information
1660 CdmaCallWaitingNotification infoCW = (CdmaCallWaitingNotification) r.result;
1661 int isPresent = infoCW.isPresent;
1662 if (DBG) log("onCdmaCallWaiting: isPresent=" + isPresent);
1663 if (isPresent == 1 ) {//'1' if tone is valid
1664 int uSignalType = infoCW.signalType;
1665 int uAlertPitch = infoCW.alertPitch;
1666 int uSignal = infoCW.signal;
1667 if (DBG) log("onCdmaCallWaiting: uSignalType=" + uSignalType + ", uAlertPitch="
1668 + uAlertPitch + ", uSignal=" + uSignal);
1669 //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1670 int toneID =
1671 SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
1672
1673 //Create the SignalInfo tone player and pass the ToneID
1674 new SignalInfoTonePlayer(toneID).start();
1675 }
1676 }
1677
1678 /**
1679 * Posts a event causing us to clean up after rejecting (or timing-out) a
1680 * CDMA call-waiting call.
1681 *
1682 * This method is safe to call from any thread.
1683 * @see #onCdmaCallWaitingReject()
1684 */
1685 /* package */ void sendCdmaCallWaitingReject() {
1686 sendEmptyMessage(CDMA_CALL_WAITING_REJECT);
1687 }
1688
1689 /**
1690 * Performs Call logging based on Timeout or Ignore Call Waiting Call for CDMA,
1691 * and finally calls Hangup on the Call Waiting connection.
1692 *
1693 * This method should be called only from the UI thread.
1694 * @see #sendCdmaCallWaitingReject()
1695 */
1696 private void onCdmaCallWaitingReject() {
1697 final Call ringingCall = mCM.getFirstActiveRingingCall();
1698
1699 // Call waiting timeout scenario
1700 if (ringingCall.getState() == Call.State.WAITING) {
1701 // Code for perform Call logging and missed call notification
1702 Connection c = ringingCall.getLatestConnection();
1703
1704 if (c != null) {
1705 final int callLogType = mCallWaitingTimeOut ?
1706 Calls.MISSED_TYPE : Calls.INCOMING_TYPE;
1707
1708 // TODO: This callLogType override is not ideal. Connection should be astracted away
1709 // at a telephony-phone layer that can understand and edit the callTypes within
1710 // the abstraction for CDMA devices.
1711 mCallLogger.logCall(c, callLogType);
1712
1713 final long date = c.getCreateTime();
1714 if (callLogType == Calls.MISSED_TYPE) {
1715 // Add missed call notification
1716 showMissedCallNotification(c, date);
1717 } else {
1718 // Remove Call waiting 20 second display timer in the queue
1719 removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
1720 }
1721
1722 // Hangup the RingingCall connection for CW
1723 PhoneUtils.hangup(c);
1724 }
1725
1726 //Reset the mCallWaitingTimeOut boolean
1727 mCallWaitingTimeOut = false;
1728 }
1729 }
1730
1731 /**
1732 * Return the private variable mPreviousCdmaCallState.
1733 */
1734 /* package */ Call.State getPreviousCdmaCallState() {
1735 return mPreviousCdmaCallState;
1736 }
1737
1738 /**
1739 * Return the private variable mVoicePrivacyState.
1740 */
1741 /* package */ boolean getVoicePrivacyState() {
1742 return mVoicePrivacyState;
1743 }
1744
1745 /**
1746 * Return the private variable mIsCdmaRedialCall.
1747 */
1748 /* package */ boolean getIsCdmaRedialCall() {
1749 return mIsCdmaRedialCall;
1750 }
1751
1752 /**
1753 * Helper function used to show a missed call notification.
1754 */
1755 private void showMissedCallNotification(Connection c, final long date) {
1756 PhoneUtils.CallerInfoToken info =
1757 PhoneUtils.startGetCallerInfo(mApplication, c, this, Long.valueOf(date));
1758 if (info != null) {
1759 // at this point, we've requested to start a query, but it makes no
1760 // sense to log this missed call until the query comes back.
1761 if (VDBG) log("showMissedCallNotification: Querying for CallerInfo on missed call...");
1762 if (info.isFinal) {
1763 // it seems that the query we have actually is up to date.
1764 // send the notification then.
1765 CallerInfo ci = info.currentInfo;
1766
1767 // Check number presentation value; if we have a non-allowed presentation,
1768 // then display an appropriate presentation string instead as the missed
1769 // call.
1770 String name = ci.name;
1771 String number = ci.phoneNumber;
1772 if (ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
1773 name = mApplication.getString(R.string.private_num);
1774 } else if (ci.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1775 name = mApplication.getString(R.string.unknown);
1776 } else {
1777 number = PhoneUtils.modifyForSpecialCnapCases(mApplication,
1778 ci, number, ci.numberPresentation);
1779 }
1780 mApplication.notificationMgr.notifyMissedCall(name, number,
1781 ci.phoneLabel, ci.cachedPhoto, ci.cachedPhotoIcon, date);
1782 }
1783 } else {
1784 // getCallerInfo() can return null in rare cases, like if we weren't
1785 // able to get a valid phone number out of the specified Connection.
1786 Log.w(LOG_TAG, "showMissedCallNotification: got null CallerInfo for Connection " + c);
1787 }
1788 }
1789
1790 /**
1791 * Inner class to handle emergency call tone and vibrator
1792 */
1793 private class EmergencyTonePlayerVibrator {
1794 private final int EMG_VIBRATE_LENGTH = 1000; // ms.
1795 private final int EMG_VIBRATE_PAUSE = 1000; // ms.
1796 private final long[] mVibratePattern =
1797 new long[] { EMG_VIBRATE_LENGTH, EMG_VIBRATE_PAUSE };
1798
1799 private ToneGenerator mToneGenerator;
1800 // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this vibrator
1801 // object will be isolated from others.
1802 private Vibrator mEmgVibrator = new SystemVibrator();
1803 private int mInCallVolume;
1804
1805 /**
1806 * constructor
1807 */
1808 public EmergencyTonePlayerVibrator() {
1809 }
1810
1811 /**
1812 * Start the emergency tone or vibrator.
1813 */
1814 private void start() {
1815 if (VDBG) log("call startEmergencyToneOrVibrate.");
1816 int ringerMode = mAudioManager.getRingerMode();
1817
1818 if ((mIsEmergencyToneOn == EMERGENCY_TONE_ALERT) &&
1819 (ringerMode == AudioManager.RINGER_MODE_NORMAL)) {
1820 log("EmergencyTonePlayerVibrator.start(): emergency tone...");
1821 mToneGenerator = new ToneGenerator (AudioManager.STREAM_VOICE_CALL,
1822 InCallTonePlayer.TONE_RELATIVE_VOLUME_EMERGENCY);
1823 if (mToneGenerator != null) {
1824 mInCallVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1825 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1826 mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL),
1827 0);
1828 mToneGenerator.startTone(ToneGenerator.TONE_CDMA_EMERGENCY_RINGBACK);
1829 mCurrentEmergencyToneState = EMERGENCY_TONE_ALERT;
1830 }
1831 } else if (mIsEmergencyToneOn == EMERGENCY_TONE_VIBRATE) {
1832 log("EmergencyTonePlayerVibrator.start(): emergency vibrate...");
1833 if (mEmgVibrator != null) {
1834 mEmgVibrator.vibrate(mVibratePattern, 0);
1835 mCurrentEmergencyToneState = EMERGENCY_TONE_VIBRATE;
1836 }
1837 }
1838 }
1839
1840 /**
1841 * If the emergency tone is active, stop the tone or vibrator accordingly.
1842 */
1843 private void stop() {
1844 if (VDBG) log("call stopEmergencyToneOrVibrate.");
1845
1846 if ((mCurrentEmergencyToneState == EMERGENCY_TONE_ALERT)
1847 && (mToneGenerator != null)) {
1848 mToneGenerator.stopTone();
1849 mToneGenerator.release();
1850 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1851 mInCallVolume,
1852 0);
1853 } else if ((mCurrentEmergencyToneState == EMERGENCY_TONE_VIBRATE)
1854 && (mEmgVibrator != null)) {
1855 mEmgVibrator.cancel();
1856 }
1857 mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
1858 }
1859 }
1860
1861 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
1862 new BluetoothProfile.ServiceListener() {
1863 public void onServiceConnected(int profile, BluetoothProfile proxy) {
1864 mBluetoothHeadset = (BluetoothHeadset) proxy;
1865 if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
1866 }
1867
1868 public void onServiceDisconnected(int profile) {
1869 mBluetoothHeadset = null;
1870 }
1871 };
1872
1873 private void onRingbackTone(AsyncResult r) {
1874 boolean playTone = (Boolean)(r.result);
1875
1876 if (playTone == true) {
1877 // Only play when foreground call is in DIALING or ALERTING.
1878 // to prevent a late coming playtone after ALERTING.
1879 // Don't play ringback tone if it is in play, otherwise it will cut
1880 // the current tone and replay it
1881 if (mCM.getActiveFgCallState().isDialing() &&
1882 mInCallRingbackTonePlayer == null) {
1883 mInCallRingbackTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_RING_BACK);
1884 mInCallRingbackTonePlayer.start();
1885 }
1886 } else {
1887 if (mInCallRingbackTonePlayer != null) {
1888 mInCallRingbackTonePlayer.stopTone();
1889 mInCallRingbackTonePlayer = null;
1890 }
1891 }
1892 }
1893
1894 /**
1895 * Toggle mute and unmute requests while keeping the same mute state
1896 */
1897 private void onResendMute() {
1898 boolean muteState = PhoneUtils.getMute();
1899 PhoneUtils.setMute(!muteState);
1900 PhoneUtils.setMute(muteState);
1901 }
1902
1903 private void log(String msg) {
1904 Log.d(LOG_TAG, msg);
1905 }
1906}