Misc phone app code cleanup.
[android/platform/packages/apps/Phone.git] / src / com / android / phone / CallNotifier.java
1 /*
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
17 package com.android.phone;
18
19 import com.android.internal.telephony.Call;
20 import com.android.internal.telephony.CallerInfo;
21 import com.android.internal.telephony.CallerInfoAsyncQuery;
22 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
23 import com.android.internal.telephony.cdma.SignalToneUtil;
24 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
25 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
26 import com.android.internal.telephony.Connection;
27 import com.android.internal.telephony.Phone;
28 import com.android.internal.telephony.gsm.GSMPhone;
29
30 import android.content.Context;
31 import android.media.AudioManager;
32 import android.media.ToneGenerator;
33 import android.os.AsyncResult;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.os.SystemClock;
37 import android.os.SystemProperties;
38 import android.provider.CallLog;
39 import android.provider.CallLog.Calls;
40 import android.provider.Checkin;
41 import android.provider.Settings;
42 import android.telephony.PhoneStateListener;
43 import android.telephony.TelephonyManager;
44 import android.util.Log;
45
46
47 /**
48  * Stub which listens for phone state changes and decides whether it is worth
49  * telling the user what just happened.
50  */
51 public class CallNotifier extends Handler
52         implements CallerInfoAsyncQuery.OnQueryCompleteListener {
53     private static final String LOG_TAG = "CallNotifier";
54     private static final boolean DBG =
55             (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
56     private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
57
58     // Strings used with Checkin.logEvent().
59     private static final String PHONE_UI_EVENT_RINGER_QUERY_ELAPSED =
60         "using default incoming call behavior";
61     private static final String PHONE_UI_EVENT_MULTIPLE_QUERY =
62         "multiple incoming call queries attempted";
63
64     // Maximum time we allow the CallerInfo query to run,
65     // before giving up and falling back to the default ringtone.
66     private static final int RINGTONE_QUERY_WAIT_TIME = 500;  // msec
67
68     // Timers related to CDMA Call Waiting
69     // 1) For displaying Caller Info
70     // 2) For disabling "Add Call" menu option once User selects Ignore or CW Timeout occures
71     private static final int CALLWAITING_CALLERINFO_DISPLAY_TIME = 20000; // msec
72     private static final int CALLWAITING_ADDCALL_DISABLE_TIME = 60000; // msec
73
74     // Time to display the  DisplayInfo Record sent by CDMA network
75     private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
76
77     // Boolean to store information if a Call Waiting timed out
78     private boolean mCallWaitingTimeOut = false;
79
80     // values used to track the query state
81     private static final int CALLERINFO_QUERY_READY = 0;
82     private static final int CALLERINFO_QUERYING = -1;
83
84     // the state of the CallerInfo Query.
85     private int mCallerInfoQueryState;
86
87     // object used to synchronize access to mCallerInfoQueryState
88     private Object mCallerInfoQueryStateGuard = new Object();
89
90     // Event used to indicate a query timeout.
91     private static final int RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT = 100;
92
93     // Events from the Phone object:
94     private static final int PHONE_STATE_CHANGED = 1;
95     private static final int PHONE_NEW_RINGING_CONNECTION = 2;
96     private static final int PHONE_DISCONNECT = 3;
97     private static final int PHONE_UNKNOWN_CONNECTION_APPEARED = 4;
98     private static final int PHONE_INCOMING_RING = 5;
99     private static final int PHONE_STATE_DISPLAYINFO = 6;
100     private static final int PHONE_STATE_SIGNALINFO = 7;
101     private static final int PHONE_CDMA_CALL_WAITING = 8;
102     // Events generated internally:
103     private static final int PHONE_MWI_CHANGED = 9;
104     private static final int PHONE_BATTERY_LOW = 10;
105     private static final int CALLWAITING_CALLERINFO_DISPLAY_DONE = 11;
106     private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 12;
107     private static final int DISPLAYINFO_NOTIFICATION_DONE = 13;
108
109     private PhoneApp mApplication;
110     private Phone mPhone;
111     private Ringer mRinger;
112     private BluetoothHandsfree mBluetoothHandsfree;
113     private boolean mSilentRingerRequested;
114
115     // ToneGenerator instance for playing SignalInfo tones
116     private ToneGenerator mSignalInfoToneGenerator;
117
118     // The tone volume relative to other sounds in the stream SignalInfo
119     private static final int TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO = 50;
120
121     public CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
122                         BluetoothHandsfree btMgr) {
123         mApplication = app;
124
125         mPhone = phone;
126         mPhone.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
127         mPhone.registerForPhoneStateChanged(this, PHONE_STATE_CHANGED, null);
128         mPhone.registerForDisconnect(this, PHONE_DISCONNECT, null);
129         mPhone.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
130         mPhone.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
131
132         if (mPhone.getPhoneName().equals("CDMA")) {
133             if (DBG) log("Registering for Call Waiting, Signal and Display Info.");
134             mPhone.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
135             mPhone.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
136             mPhone.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
137
138             // Instantiate the ToneGenerator for SignalInfo and CallWaiting
139             // TODO(Moto): We probably dont need the mSignalInfoToneGenerator instance
140             // around forever. Need to change it so as to create a ToneGenerator instance only
141             // when a tone is being played and releases it after its done playing.
142             try {
143                 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION,
144                         TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO);
145             } catch (RuntimeException e) {
146                 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
147                         "mSignalInfoToneGenerator: " + e);
148                 mSignalInfoToneGenerator = null;
149             }
150         }
151
152         mRinger = ringer;
153         mBluetoothHandsfree = btMgr;
154
155         TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
156                 Context.TELEPHONY_SERVICE);
157         telephonyManager.listen(mPhoneStateListener,
158                 PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
159                 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
160     }
161
162     @Override
163     public void handleMessage(Message msg) {
164         switch (msg.what) {
165             case PHONE_NEW_RINGING_CONNECTION:
166                 if (DBG) log("RINGING... (new)");
167                 onNewRingingConnection((AsyncResult) msg.obj);
168                 mSilentRingerRequested = false;
169                 break;
170
171             case PHONE_INCOMING_RING:
172                 // repeat the ring when requested by the RIL, and when the user has NOT
173                 // specifically requested silence.
174                 if (msg.obj != null && ((AsyncResult) msg.obj).result != null &&
175                         ((GSMPhone)((AsyncResult) msg.obj).result).getState() == Phone.State.RINGING
176                         && mSilentRingerRequested == false) {
177                     if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
178                     mRinger.ring();
179                 } else {
180                     if (DBG) log("RING before NEW_RING, skipping");
181                 }
182                 break;
183
184             case PHONE_STATE_CHANGED:
185                 onPhoneStateChanged((AsyncResult) msg.obj);
186                 break;
187
188             case PHONE_DISCONNECT:
189                 if (DBG) log("DISCONNECT");
190                 onDisconnect((AsyncResult) msg.obj);
191                 break;
192
193             case PHONE_UNKNOWN_CONNECTION_APPEARED:
194                 onUnknownConnectionAppeared((AsyncResult) msg.obj);
195                 break;
196
197             case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
198                 // CallerInfo query is taking too long!  But we can't wait
199                 // any more, so start ringing NOW even if it means we won't
200                 // use the correct custom ringtone.
201                 Log.w(LOG_TAG, "CallerInfo query took too long; manually starting ringer");
202
203                 // In this case we call onCustomRingQueryComplete(), just
204                 // like if the query had completed normally.  (But we're
205                 // going to get the default ringtone, since we never got
206                 // the chance to call Ringer.setCustomRingtoneUri()).
207                 onCustomRingQueryComplete();
208                 break;
209
210             case PHONE_MWI_CHANGED:
211                 onMwiChanged(mPhone.getMessageWaitingIndicator());
212                 break;
213
214             case PHONE_BATTERY_LOW:
215                 onBatteryLow();
216                 break;
217
218             case PHONE_CDMA_CALL_WAITING:
219                 if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
220                 onCdmaCallWaiting((AsyncResult) msg.obj);
221                 break;
222
223             case CALLWAITING_CALLERINFO_DISPLAY_DONE:
224                 if (DBG) log("Received CALLWAITING_CALLERINFO_DISPLAY_DONE event ...");
225                 mCallWaitingTimeOut = true;
226                 onCdmaCallWaitingReject();
227                 break;
228
229             case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
230                 if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
231                 // Set the mAddCallMenuStateAfterCW state to true
232                 mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
233                 break;
234
235             case PHONE_STATE_DISPLAYINFO:
236                 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
237                 onDisplayInfo((AsyncResult) msg.obj);
238                 break;
239
240             case PHONE_STATE_SIGNALINFO:
241                 if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
242                 onSignalInfo((AsyncResult) msg.obj);
243                 break;
244
245             case DISPLAYINFO_NOTIFICATION_DONE:
246                 if (DBG) log("Received Display Info notification done event ...");
247                 CdmaDisplayInfo.dismissDisplayInfoRecord();
248                 break;
249
250             default:
251                 // super.handleMessage(msg);
252         }
253     }
254
255     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
256         @Override
257         public void onMessageWaitingIndicatorChanged(boolean mwi) {
258             onMwiChanged(mwi);
259         }
260
261         @Override
262         public void onCallForwardingIndicatorChanged(boolean cfi) {
263             onCfiChanged(cfi);
264         }
265     };
266
267     private void onNewRingingConnection(AsyncResult r) {
268         Connection c = (Connection) r.result;
269         if (DBG) log("onNewRingingConnection(): " + c);
270
271         // Incoming calls are totally ignored if the device isn't provisioned yet
272         boolean provisioned = Settings.Secure.getInt(mPhone.getContext().getContentResolver(),
273             Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
274         if (!provisioned) {
275             Log.i(LOG_TAG, "CallNotifier: rejecting incoming call: device isn't provisioned");
276             // Send the caller straight to voicemail, just like
277             // "rejecting" an incoming call.
278             PhoneUtils.hangupRingingCall(mPhone);
279             return;
280         }
281
282         if (c != null && c.isRinging()) {
283             Call.State state = c.getState();
284             // State will be either INCOMING or WAITING.
285             if (VDBG) log("- connection is ringing!  state = " + state);
286             // if (DBG) PhoneUtils.dumpCallState(mPhone);
287
288             // No need to do any service state checks here (like for
289             // "emergency mode"), since in those states the SIM won't let
290             // us get incoming connections in the first place.
291
292             // TODO: Consider sending out a serialized broadcast Intent here
293             // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
294             // ringer and going to the in-call UI.  The intent should contain
295             // the caller-id info for the current connection, and say whether
296             // it would be a "call waiting" call or a regular ringing call.
297             // If anybody consumed the broadcast, we'd bail out without
298             // ringing or bringing up the in-call UI.
299             //
300             // This would give 3rd party apps a chance to listen for (and
301             // intercept) new ringing connections.  An app could reject the
302             // incoming call by consuming the broadcast and doing nothing, or
303             // it could "pick up" the call (without any action by the user!)
304             // by firing off an ACTION_ANSWER intent.
305             //
306             // We'd need to protect this with a new "intercept incoming calls"
307             // system permission.
308
309             // - don't ring for call waiting connections
310             // - do this before showing the incoming call panel
311             if (state == Call.State.INCOMING) {
312                 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_RINGING);
313                 startIncomingCallQuery(c);
314             } else {
315                 if (VDBG) log("- starting call waiting tone...");
316                 new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING).start();
317                 // The InCallTonePlayer will automatically stop playing (and
318                 // clean itself up) after playing the tone.
319
320                 // TODO: alternatively, consider starting an
321                 // InCallTonePlayer with an "unlimited" tone length, and
322                 // manually stop it later when the ringing call either (a)
323                 // gets answered, or (b) gets disconnected.
324
325                 // in this case, just fall through like before, and call
326                 // PhoneUtils.showIncomingCallUi
327                 PhoneUtils.showIncomingCallUi();
328             }
329         }
330
331         // Obtain a partial wake lock to make sure the CPU doesn't go to
332         // sleep before we finish bringing up the InCallScreen.
333         // (This will be upgraded soon to a full wake lock; see
334         // PhoneUtils.showIncomingCallUi().)
335         if (VDBG) log("Holding wake lock on new incoming connection.");
336         mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL);
337
338         if (VDBG) log("- onNewRingingConnection() done.");
339     }
340
341     /**
342      * Helper method to manage the start of incoming call queries
343      */
344     private void startIncomingCallQuery(Connection c) {
345         // TODO: cache the custom ringer object so that subsequent
346         // calls will not need to do this query work.  We can keep
347         // the MRU ringtones in memory.  We'll still need to hit
348         // the database to get the callerinfo to act as a key,
349         // but at least we can save the time required for the
350         // Media player setup.  The only issue with this is that
351         // we may need to keep an eye on the resources the Media
352         // player uses to keep these ringtones around.
353
354         // make sure we're in a state where we can be ready to
355         // query a ringtone uri.
356         boolean shouldStartQuery = false;
357         synchronized (mCallerInfoQueryStateGuard) {
358             if (mCallerInfoQueryState == CALLERINFO_QUERY_READY) {
359                 mCallerInfoQueryState = CALLERINFO_QUERYING;
360                 shouldStartQuery = true;
361             }
362         }
363         if (shouldStartQuery) {
364             // create a custom ringer using the default ringer first
365             mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
366
367             // query the callerinfo to try to get the ringer.
368             PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
369                     mPhone.getContext(), c, this, this);
370
371             // if this has already been queried then just ring, otherwise
372             // we wait for the alloted time before ringing.
373             if (cit.isFinal) {
374                 if (VDBG) log("- CallerInfo already up to date, using available data");
375                 onQueryComplete(0, this, cit.currentInfo);
376             } else {
377                 if (VDBG) log("- Starting query, posting timeout message.");
378                 sendEmptyMessageDelayed(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT,
379                         RINGTONE_QUERY_WAIT_TIME);
380             }
381             // calls to PhoneUtils.showIncomingCallUi will come after the
382             // queries are complete (or timeout).
383         } else {
384             // This should never happen; its the case where an incoming call
385             // arrives at the same time that the query is still being run,
386             // and before the timeout window has closed.
387             Checkin.logEvent(mPhone.getContext().getContentResolver(),
388                     Checkin.Events.Tag.PHONE_UI,
389                     PHONE_UI_EVENT_MULTIPLE_QUERY);
390
391             // In this case, just log the request and ring.
392             if (VDBG) log("RINGING... (request to ring arrived while query is running)");
393             mRinger.ring();
394
395             // in this case, just fall through like before, and call
396             // PhoneUtils.showIncomingCallUi
397             PhoneUtils.showIncomingCallUi();
398         }
399     }
400
401     /**
402      * Performs the final steps of the onNewRingingConnection sequence:
403      * starts the ringer, and launches the InCallScreen to show the
404      * "incoming call" UI.
405      *
406      * Normally, this is called when the CallerInfo query completes (see
407      * onQueryComplete()).  In this case, onQueryComplete() has already
408      * configured the Ringer object to use the custom ringtone (if there
409      * is one) for this caller.  So we just tell the Ringer to start, and
410      * proceed to the InCallScreen.
411      *
412      * But this method can *also* be called if the
413      * RINGTONE_QUERY_WAIT_TIME timeout expires, which means that the
414      * CallerInfo query is taking too long.  In that case, we log a
415      * warning but otherwise we behave the same as in the normal case.
416      * (We still tell the Ringer to start, but it's going to use the
417      * default ringtone.)
418      */
419     private void onCustomRingQueryComplete() {
420         boolean isQueryExecutionTimeExpired = false;
421         synchronized (mCallerInfoQueryStateGuard) {
422             if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
423                 mCallerInfoQueryState = CALLERINFO_QUERY_READY;
424                 isQueryExecutionTimeExpired = true;
425             }
426         }
427         if (isQueryExecutionTimeExpired) {
428             // There may be a problem with the query here, since the
429             // default ringtone is playing instead of the custom one.
430             Log.w(LOG_TAG, "CallerInfo query took too long; falling back to default ringtone");
431             Checkin.logEvent(mPhone.getContext().getContentResolver(),
432                     Checkin.Events.Tag.PHONE_UI,
433                     PHONE_UI_EVENT_RINGER_QUERY_ELAPSED);
434         }
435
436         // Make sure we still have an incoming call!
437         //
438         // (It's possible for the incoming call to have been disconnected
439         // while we were running the query.  In that case we better not
440         // start the ringer here, since there won't be any future
441         // DISCONNECT event to stop it!)
442         //
443         // Note we don't have to worry about the incoming call going away
444         // *after* this check but before we call mRinger.ring() below,
445         // since in that case we *will* still get a DISCONNECT message sent
446         // to our handler.  (And we will correctly stop the ringer when we
447         // process that event.)
448         if (mPhone.getState() != Phone.State.RINGING) {
449             Log.i(LOG_TAG, "onCustomRingQueryComplete: No incoming call! Bailing out...");
450             // Don't start the ringer *or* bring up the "incoming call" UI.
451             // Just bail out.
452             return;
453         }
454
455         // Ring, either with the queried ringtone or default one.
456         if (VDBG) log("RINGING... (onCustomRingQueryComplete)");
457         mRinger.ring();
458
459         // ...and show the InCallScreen.
460         PhoneUtils.showIncomingCallUi();
461     }
462
463     private void onUnknownConnectionAppeared(AsyncResult r) {
464         Phone.State state = mPhone.getState();
465
466         if (state == Phone.State.OFFHOOK) {
467             // basically do onPhoneStateChanged + displayCallScreen
468             onPhoneStateChanged(r);
469             PhoneUtils.showIncomingCallUi();
470         }
471     }
472
473     private void onPhoneStateChanged(AsyncResult r) {
474         Phone.State state = mPhone.getState();
475
476         // Turn status bar notifications on or off depending upon the state
477         // of the phone.  Notification Alerts (audible or vibrating) should
478         // be on if and only if the phone is IDLE.
479         NotificationMgr.getDefault().getStatusBarMgr()
480                 .enableNotificationAlerts(state == Phone.State.IDLE);
481
482         // Have the PhoneApp recompute its mShowBluetoothIndication
483         // flag based on the (new) telephony state.
484         // There's no need to force a UI update since we update the
485         // in-call notification ourselves (below), and the InCallScreen
486         // listens for phone state changes itself.
487         mApplication.updateBluetoothIndication(false);
488
489         if (state == Phone.State.OFFHOOK) {
490             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
491             if (VDBG) log("onPhoneStateChanged: OFF HOOK");
492
493             // if the call screen is showing, let it handle the event,
494             // otherwise handle it here.
495             if (!mApplication.isShowingCallScreen()) {
496                 mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
497                 mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
498             }
499
500             // Since we're now in-call, the Ringer should definitely *not*
501             // be ringing any more.  (This is just a sanity-check; we
502             // already stopped the ringer explicitly back in
503             // PhoneUtils.answerCall(), before the call to phone.acceptCall().)
504             // TODO: Confirm that this call really *is* unnecessary, and if so,
505             // remove it!
506             if (DBG) log("stopRing()... (OFFHOOK state)");
507             mRinger.stopRing();
508
509             // put a icon in the status bar
510             NotificationMgr.getDefault().updateInCallNotification();
511         }
512     }
513
514     void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
515         if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
516         // Unregister all events from the old obsolete phone
517         mPhone.unregisterForNewRingingConnection(this);
518         mPhone.unregisterForPhoneStateChanged(this);
519         mPhone.unregisterForDisconnect(this);
520         mPhone.unregisterForUnknownConnection(this);
521         mPhone.unregisterForIncomingRing(this);
522         mPhone.unregisterForCallWaiting(this);
523         mPhone.unregisterForDisplayInfo(this);
524         mPhone.unregisterForSignalInfo(this);
525
526         // Release the ToneGenerator used for playing SignalInfo and CallWaiting
527         if (mSignalInfoToneGenerator != null) {
528             mSignalInfoToneGenerator.release();
529         }
530
531         // Register all events new to the new active phone
532         mPhone.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
533         mPhone.registerForPhoneStateChanged(this, PHONE_STATE_CHANGED, null);
534         mPhone.registerForDisconnect(this, PHONE_DISCONNECT, null);
535         mPhone.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
536         mPhone.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
537         if (mPhone.getPhoneName().equals("CDMA")) {
538             if (DBG) log("Registering for Call Waiting, Signal and Display Info.");
539             mPhone.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
540             mPhone.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
541             mPhone.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
542
543             // Instantiate the ToneGenerator for SignalInfo
544             try {
545                 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION,
546                         TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO);
547             } catch (RuntimeException e) {
548                 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
549                         "mSignalInfoToneGenerator: " + e);
550                 mSignalInfoToneGenerator = null;
551             }
552         }
553     }
554
555     /**
556      * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
557      * refreshes the CallCard data when it called.  If called with this
558      * class itself, it is assumed that we have been waiting for the ringtone
559      * and direct to voicemail settings to update.
560      */
561     public void onQueryComplete(int token, Object cookie, CallerInfo ci){
562         if (cookie instanceof Long) {
563             if (VDBG) log("CallerInfo query complete, posting missed call notification");
564
565             NotificationMgr.getDefault().notifyMissedCall(ci.name, ci.phoneNumber,
566                     ci.phoneLabel, ((Long) cookie).longValue());
567         } else if (cookie instanceof CallNotifier){
568             if (VDBG) log("CallerInfo query complete, updating data");
569
570             // get rid of the timeout messages
571             removeMessages(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT);
572
573             boolean isQueryExecutionTimeOK = false;
574             synchronized (mCallerInfoQueryStateGuard) {
575                 if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
576                     mCallerInfoQueryState = CALLERINFO_QUERY_READY;
577                     isQueryExecutionTimeOK = true;
578                 }
579             }
580             //if we're in the right state
581             if (isQueryExecutionTimeOK) {
582
583                 // send directly to voicemail.
584                 if (ci.shouldSendToVoicemail) {
585                     if (DBG) log("send to voicemail flag detected. hanging up.");
586                     PhoneUtils.hangupRingingCall(mPhone);
587                     return;
588                 }
589
590                 // set the ringtone uri to prepare for the ring.
591                 if (ci.contactRingtoneUri != null) {
592                     if (DBG) log("custom ringtone found, setting up ringer.");
593                     Ringer r = ((CallNotifier) cookie).mRinger;
594                     r.setCustomRingtoneUri(ci.contactRingtoneUri);
595                 }
596                 // ring, and other post-ring actions.
597                 onCustomRingQueryComplete();
598             }
599         }
600     }
601
602     private void onDisconnect(AsyncResult r) {
603         if (VDBG) log("onDisconnect()...  phone state: " + mPhone.getState());
604         if (mPhone.getState() == Phone.State.IDLE) {
605             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
606         }
607
608         if (mPhone.getPhoneName().equals("CDMA")) {
609             // Create the SignalInfo tone player to stop any signalInfo tone being played.
610             new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
611         }
612
613         Connection c = (Connection) r.result;
614         if (DBG && c != null) {
615             log("- onDisconnect: cause = " + c.getDisconnectCause()
616                 + ", incoming = " + c.isIncoming()
617                 + ", date = " + c.getCreateTime());
618         }
619
620         // Stop the ringer if it was ringing (for an incoming call that
621         // either disconnected by itself, or was rejected by the user.)
622         //
623         // TODO: We technically *shouldn't* stop the ringer if the
624         // foreground or background call disconnects while an incoming call
625         // is still ringing, but that's a really rare corner case.
626         // It's safest to just unconditionally stop the ringer here.
627         if (DBG) log("stopRing()... (onDisconnect)");
628         mRinger.stopRing();
629
630         // Check for the various tones we might need to play (thru the
631         // earpiece) after a call disconnects.
632         int toneToPlay = InCallTonePlayer.TONE_NONE;
633
634         // The "Busy" or "Congestion" tone is the highest priority:
635         if (c != null) {
636             Connection.DisconnectCause cause = c.getDisconnectCause();
637             if (cause == Connection.DisconnectCause.BUSY) {
638                 if (DBG) log("- need to play BUSY tone!");
639                 toneToPlay = InCallTonePlayer.TONE_BUSY;
640             } else if (cause == Connection.DisconnectCause.CONGESTION) {
641                 if (DBG) log("- need to play CONGESTION tone!");
642                 toneToPlay = InCallTonePlayer.TONE_CONGESTION;
643             }
644         }
645
646         // If we don't need to play BUSY or CONGESTION, then play the
647         // "call ended" tone if this was a "regular disconnect" (i.e. a
648         // normal call where one end or the other hung up) *and* this
649         // disconnect event caused the phone to become idle.  (In other
650         // words, we *don't* play the sound if one call hangs up but
651         // there's still an active call on the other line.)
652         // TODO: We may eventually want to disable this via a preference.
653         if ((toneToPlay == InCallTonePlayer.TONE_NONE)
654             && (mPhone.getState() == Phone.State.IDLE)
655             && (c != null)) {
656             Connection.DisconnectCause cause = c.getDisconnectCause();
657             if ((cause == Connection.DisconnectCause.NORMAL)  // remote hangup
658                 || (cause == Connection.DisconnectCause.LOCAL)) {  // local hangup
659                 if (VDBG) log("- need to play CALL_ENDED tone!");
660                 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
661             }
662         }
663
664         if (mPhone.getState() == Phone.State.IDLE) {
665             // Don't reset the audio mode or bluetooth/speakerphone state
666             // if we still need to let the user hear a tone through the earpiece.
667             if (toneToPlay == InCallTonePlayer.TONE_NONE) {
668                 resetAudioStateAfterDisconnect();
669             }
670
671             NotificationMgr.getDefault().cancelCallInProgressNotification();
672
673             // If the InCallScreen is *not* in the foreground, forcibly
674             // dismiss it to make sure it won't still be in the activity
675             // history.  (But if it *is* in the foreground, don't mess
676             // with it; it needs to be visible, displaying the "Call
677             // ended" state.)
678             if (!mApplication.isShowingCallScreen()) {
679                 if (VDBG) log("onDisconnect: force InCallScreen to finish()");
680                 mApplication.dismissCallScreen();
681             }
682         }
683
684         if (c != null) {
685             final String number = c.getAddress();
686             final int presentation = c.getNumberPresentation();
687             if (DBG) log("- onDisconnect: presentation=" + presentation);
688             final long date = c.getCreateTime();
689             final long duration = c.getDurationMillis();
690             final Connection.DisconnectCause cause = c.getDisconnectCause();
691
692             // Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
693             final int callLogType;
694             if (c.isIncoming()) {
695                 callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
696                                CallLog.Calls.MISSED_TYPE :
697                                CallLog.Calls.INCOMING_TYPE);
698             } else {
699                 callLogType = CallLog.Calls.OUTGOING_TYPE;
700             }
701             if (VDBG) log("- callLogType: " + callLogType + ", UserData: " + c.getUserData());
702
703             // Get the CallerInfo object and then log the call with it.
704             {
705                 Object o = c.getUserData();
706                 final CallerInfo ci;
707                 if ((o == null) || (o instanceof CallerInfo)){
708                     ci = (CallerInfo) o;
709                 } else {
710                     ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
711                 }
712
713                 // Watch out: Calls.addCall() hits the Contacts database,
714                 // so we shouldn't call it from the main thread.
715                 Thread t = new Thread() {
716                         public void run() {
717                             Calls.addCall(ci, mApplication, number, presentation,
718                                           callLogType, date, (int) duration / 1000);
719                             // if (DBG) log("onDisconnect helper thread: Calls.addCall() done.");
720                         }
721                     };
722                 t.start();
723             }
724
725             if (callLogType == CallLog.Calls.MISSED_TYPE) {
726                 // Show the "Missed call" notification.
727                 // (Note we *don't* do this if this was an incoming call that
728                 // the user deliberately rejected.)
729
730                 PhoneUtils.CallerInfoToken info =
731                         PhoneUtils.startGetCallerInfo(mApplication, c, this, Long.valueOf(date));
732                 if (info != null) {
733                     // at this point, we've requested to start a query, but it makes no
734                     // sense to log this missed call until the query comes back.
735                     if (VDBG) log("onDisconnect: Querying for CallerInfo on missed call...");
736                     if (info.isFinal) {
737                         // it seems that the query we have actually is up to date.
738                         // send the notification then.
739                         CallerInfo ci = info.currentInfo;
740                         NotificationMgr.getDefault().notifyMissedCall(ci.name, ci.phoneNumber,
741                                 ci.phoneLabel, date);
742                     }
743                 } else {
744                     // getCallerInfo() can return null in rare cases, like if we weren't
745                     // able to get a valid phone number out of the specified Connection.
746                     Log.w(LOG_TAG, "onDisconnect: got null CallerInfo for Connection " + c);
747                 }
748             }
749
750             // Possibly play a "post-disconnect tone" thru the earpiece.
751             // We do this here, rather than from the InCallScreen
752             // activity, since we need to do this even if you're not in
753             // the Phone UI at the moment the connection ends.
754             if (toneToPlay != InCallTonePlayer.TONE_NONE) {
755                 if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
756                 new InCallTonePlayer(toneToPlay).start();
757                 // The InCallTonePlayer will automatically stop playing (and
758                 // clean itself up) after a few seconds.
759
760                 // TODO: alternatively, we could start an InCallTonePlayer
761                 // here with an "unlimited" tone length,
762                 // and manually stop it later when this connection truly goes
763                 // away.  (The real connection over the network was closed as soon
764                 // as we got the BUSY message.  But our telephony layer keeps the
765                 // connection open for a few extra seconds so we can show the
766                 // "busy" indication to the user.  We could stop the busy tone
767                 // when *that* connection's "disconnect" event comes in.)
768             }
769
770             if (mPhone.getState() == Phone.State.IDLE) {
771                 // Release screen wake locks if the in-call screen is not
772                 // showing. Otherwise, let the in-call screen handle this because
773                 // it needs to show the call ended screen for a couple of
774                 // seconds.
775                 if (!mApplication.isShowingCallScreen()) {
776                     if (VDBG) log("- NOT showing in-call screen; releasing wake locks!");
777                     mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
778                     mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
779                 } else {
780                     if (VDBG) log("- still showing in-call screen; not releasing wake locks.");
781                 }
782             } else {
783                 if (VDBG) log("- phone still in use; not releasing wake locks.");
784             }
785         }
786
787         if (mPhone.getPhoneName().equals("CDMA")) {
788             // Resetting the CdmaPhoneCallState members
789             mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
790
791             // Remove Call waiting timers
792             removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
793             removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
794         }
795     }
796
797     /**
798      * Resets the audio mode and speaker state when a call ends.
799      */
800     private void resetAudioStateAfterDisconnect() {
801         if (VDBG) log("resetAudioStateAfterDisconnect()...");
802
803         if (mBluetoothHandsfree != null) {
804             mBluetoothHandsfree.audioOff();
805         }
806
807         if (PhoneUtils.isSpeakerOn(mPhone.getContext())) {
808             PhoneUtils.turnOnSpeaker(mPhone.getContext(), false);
809         }
810
811         PhoneUtils.setAudioMode(mPhone.getContext(), AudioManager.MODE_NORMAL);
812     }
813
814     private void onMwiChanged(boolean visible) {
815         if (VDBG) log("onMwiChanged(): " + visible);
816         NotificationMgr.getDefault().updateMwi(visible);
817     }
818
819     /**
820      * Posts a delayed PHONE_MWI_CHANGED event, to schedule a "retry" for a
821      * failed NotificationMgr.updateMwi() call.
822      */
823     /* package */ void sendMwiChangedDelayed(long delayMillis) {
824         Message message = Message.obtain(this, PHONE_MWI_CHANGED);
825         sendMessageDelayed(message, delayMillis);
826     }
827
828     private void onCfiChanged(boolean visible) {
829         if (VDBG) log("onCfiChanged(): " + visible);
830         NotificationMgr.getDefault().updateCfi(visible);
831     }
832
833     /**
834      * Indicates whether or not this ringer is ringing.
835      */
836     boolean isRinging() {
837         return mRinger.isRinging();
838     }
839
840     /**
841      * Stops the current ring, and tells the notifier that future
842      * ring requests should be ignored.
843      */
844     void silenceRinger() {
845         mSilentRingerRequested = true;
846         if (DBG) log("stopRing()... (silenceRinger)");
847         mRinger.stopRing();
848     }
849
850     /**
851      * Posts a PHONE_BATTERY_LOW event, causing us to play a warning
852      * tone if the user is in-call.
853      */
854     /* package */ void sendBatteryLow() {
855         Message message = Message.obtain(this, PHONE_BATTERY_LOW);
856         sendMessage(message);
857     }
858
859     private void onBatteryLow() {
860         if (DBG) log("onBatteryLow()...");
861
862         // Play the "low battery" warning tone, only if the user is
863         // in-call.  (The test here is exactly the opposite of the test in
864         // StatusBarPolicy.updateBattery(), where we bring up the "low
865         // battery warning" dialog only if the user is NOT in-call.)
866         if (mPhone.getState() != Phone.State.IDLE) {
867             new InCallTonePlayer(InCallTonePlayer.TONE_BATTERY_LOW).start();
868         }
869     }
870
871
872     /**
873      * Helper class to play tones through the earpiece (or speaker / BT)
874      * during a call, using the ToneGenerator.
875      *
876      * To use, just instantiate a new InCallTonePlayer
877      * (passing in the TONE_* constant for the tone you want)
878      * and start() it.
879      *
880      * When we're done playing the tone, if the phone is idle at that
881      * point, we'll reset the audio routing and speaker state.
882      * (That means that for tones that get played *after* a call
883      * disconnects, like "busy" or "congestion" or "call ended", you
884      * should NOT call resetAudioStateAfterDisconnect() yourself.
885      * Instead, just start the InCallTonePlayer, which will automatically
886      * defer the resetAudioStateAfterDisconnect() call until the tone
887      * finishes playing.)
888      */
889     private class InCallTonePlayer extends Thread {
890         private int mToneId;
891
892         // The possible tones we can play.
893         public static final int TONE_NONE = 0;
894         public static final int TONE_CALL_WAITING = 1;
895         public static final int TONE_BUSY = 2;
896         public static final int TONE_CONGESTION = 3;
897         public static final int TONE_BATTERY_LOW = 4;
898         public static final int TONE_CALL_ENDED = 5;
899
900         // The tone volume relative to other sounds in the stream
901         private static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
902         private static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
903
904         InCallTonePlayer(int toneId) {
905             super();
906             mToneId = toneId;
907         }
908
909         @Override
910         public void run() {
911             if (VDBG) log("InCallTonePlayer.run(toneId = " + mToneId + ")...");
912
913             int toneType;  // passed to ToneGenerator.startTone()
914             int toneVolume;  // passed to the ToneGenerator constructor
915             int toneLengthMillis;
916             switch (mToneId) {
917                 case TONE_CALL_WAITING:
918                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
919                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
920                     toneLengthMillis = 5000;
921                     break;
922                 case TONE_BUSY:
923                     toneType = ToneGenerator.TONE_SUP_BUSY;
924                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
925                     toneLengthMillis = 4000;
926                     break;
927                 case TONE_CONGESTION:
928                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
929                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
930                     toneLengthMillis = 4000;
931                     break;
932                 case TONE_BATTERY_LOW:
933                     // For now, use ToneGenerator.TONE_PROP_ACK (two quick
934                     // beeps).  TODO: is there some other ToneGenerator
935                     // tone that would be more appropriate here?  Or
936                     // should we consider adding a new custom tone?
937                     toneType = ToneGenerator.TONE_PROP_ACK;
938                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
939                     toneLengthMillis = 1000;
940                     break;
941                 case TONE_CALL_ENDED:
942                     toneType = ToneGenerator.TONE_PROP_PROMPT;
943                     toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
944                     toneLengthMillis = 2000;
945                     break;
946                 default:
947                     throw new IllegalArgumentException("Bad toneId: " + mToneId);
948             }
949
950             // If the mToneGenerator creation fails, just continue without it.  It is
951             // a local audio signal, and is not as important.
952             ToneGenerator toneGenerator;
953             try {
954                 int stream;
955                 if (mBluetoothHandsfree != null) {
956                     stream = mBluetoothHandsfree.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
957                         AudioManager.STREAM_VOICE_CALL;
958                 } else {
959                     stream = AudioManager.STREAM_VOICE_CALL;
960                 }
961                 toneGenerator = new ToneGenerator(stream, toneVolume);
962                 // if (DBG) log("- created toneGenerator: " + toneGenerator);
963             } catch (RuntimeException e) {
964                 Log.w(LOG_TAG,
965                       "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
966                 toneGenerator = null;
967             }
968
969             // Using the ToneGenerator (with the CALL_WAITING / BUSY /
970             // CONGESTION tones at least), the ToneGenerator itself knows
971             // the right pattern of tones to play; we do NOT need to
972             // manually start/stop each individual tone, or manually
973             // insert the correct delay between tones.  (We just start it
974             // and let it run for however long we want the tone pattern to
975             // continue.)
976             //
977             // TODO: When we stop the ToneGenerator in the middle of a
978             // "tone pattern", it sounds bad if we cut if off while the
979             // tone is actually playing.  Consider adding API to the
980             // ToneGenerator to say "stop at the next silent part of the
981             // pattern", or simply "play the pattern N times and then
982             // stop."
983
984             if (toneGenerator != null) {
985                 toneGenerator.startTone(toneType);
986                 SystemClock.sleep(toneLengthMillis);
987                 toneGenerator.stopTone();
988
989                 // if (DBG) log("- InCallTonePlayer: done playing.");
990                 toneGenerator.release();
991             }
992
993             // Finally, do the same cleanup we otherwise would have done
994             // in onDisconnect().
995             //
996             // (But watch out: do NOT do this if the phone is in use,
997             // since some of our tones get played *during* a call (like
998             // CALL_WAITING and BATTERY_LOW) and we definitely *don't*
999             // want to reset the audio mode / speaker / bluetooth after
1000             // playing those!
1001             // This call is really here for use with tones that get played
1002             // *after* a call disconnects, like "busy" or "congestion" or
1003             // "call ended", where the phone has already become idle but
1004             // we need to defer the resetAudioStateAfterDisconnect() call
1005             // till the tone finishes playing.)
1006             if (mPhone.getState() == Phone.State.IDLE) {
1007                 resetAudioStateAfterDisconnect();
1008             }
1009         }
1010     }
1011
1012     /**
1013      * Displays a notification when the phone receives a DisplayInfo record.
1014      */
1015     private void onDisplayInfo(AsyncResult r) {
1016         // Extract the DisplayInfo String from the message
1017         CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
1018
1019         if (displayInfoRec != null) {
1020             String displayInfo = displayInfoRec.alpha;
1021             if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
1022             CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
1023
1024             // start a 2 second timer
1025             sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
1026                     DISPLAYINFO_NOTIFICATION_TIME);
1027         }
1028     }
1029
1030     /**
1031      * Helper class to play SignalInfo tones using the ToneGenerator.
1032      *
1033      * To use, just instantiate a new SignalInfoTonePlayer
1034      * (passing in the ToneID constant for the tone you want)
1035      * and start() it.
1036      */
1037     private class SignalInfoTonePlayer extends Thread {
1038         private int mToneId;
1039
1040         SignalInfoTonePlayer(int toneId) {
1041             super();
1042             mToneId = toneId;
1043         }
1044
1045         @Override
1046         public void run() {
1047             if (DBG) log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
1048
1049             if (mSignalInfoToneGenerator != null) {
1050                 //First stop any ongoing SignalInfo tone
1051                 mSignalInfoToneGenerator.stopTone();
1052
1053                 //Start playing the new tone if its a valid tone
1054                 mSignalInfoToneGenerator.startTone(mToneId);
1055             }
1056         }
1057     }
1058
1059     /**
1060      * Plays a tone when the phone receives a SignalInfo record.
1061      */
1062     private void onSignalInfo(AsyncResult r) {
1063         // Extract the SignalInfo String from the message
1064         CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
1065         // Only proceed if a Signal info is present.
1066         if (signalInfoRec != null) {
1067             boolean isPresent = signalInfoRec.isPresent;
1068             if (DBG) log("onSignalInfo: isPresent=" + isPresent);
1069             if (isPresent) {// if tone is valid
1070                 int uSignalType = signalInfoRec.signalType;
1071                 int uAlertPitch = signalInfoRec.alertPitch;
1072                 int uSignal = signalInfoRec.signal;
1073
1074                 if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
1075                         uAlertPitch + ", uSignal=" + uSignal);
1076                 //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1077                 int toneID =
1078                         SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
1079
1080                 //Create the SignalInfo tone player and pass the ToneID
1081                 new SignalInfoTonePlayer(toneID).start();
1082             }
1083         }
1084     }
1085
1086     /**
1087      * Plays a Call waiting tone if it is present in the second incoming call.
1088      */
1089     private void onCdmaCallWaiting(AsyncResult r) {
1090         // Start the InCallScreen Activity if its not on foreground
1091         if (!mApplication.isShowingCallScreen()) {
1092             PhoneUtils.showIncomingCallUi();
1093         }
1094
1095         // Start timer for CW display
1096         mCallWaitingTimeOut = false;
1097         sendEmptyMessageDelayed(CALLWAITING_CALLERINFO_DISPLAY_DONE,
1098                 CALLWAITING_CALLERINFO_DISPLAY_TIME);
1099
1100         // Set the mAddCallMenuStateAfterCW state to false
1101         mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(false);
1102
1103         // Start the timer for disabling "Add Call" menu option
1104         sendEmptyMessageDelayed(CALLWAITING_ADDCALL_DISABLE_TIMEOUT,
1105                 CALLWAITING_ADDCALL_DISABLE_TIME);
1106
1107         // Extract the Call waiting information
1108         CdmaCallWaitingNotification infoCW = (CdmaCallWaitingNotification) r.result;
1109         int isPresent = infoCW.isPresent;
1110         if (DBG) log("onCdmaCallWaiting: isPresent=" + isPresent);
1111         if (isPresent == 1 ) {//'1' if tone is valid
1112             int uSignalType = infoCW.signalType;
1113             int uAlertPitch = infoCW.alertPitch;
1114             int uSignal = infoCW.signal;
1115             if (DBG) log("onCdmaCallWaiting: uSignalType=" + uSignalType + ", uAlertPitch="
1116                     + uAlertPitch + ", uSignal=" + uSignal);
1117             //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1118             int toneID =
1119                 SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
1120
1121             //Create the SignalInfo tone player and pass the ToneID
1122             new SignalInfoTonePlayer(toneID).start();
1123         }
1124     }
1125
1126     /**
1127      * Performs Call logging based on Timeout or Ignore Call Waiting Call for CDMA,
1128      * and finally calls Hangup on the Call Waiting connection.
1129      */
1130     /* package */ void onCdmaCallWaitingReject() {
1131         final Call ringingCall = mPhone.getRingingCall();
1132
1133         // Call waiting timeout scenario
1134         if (ringingCall.getState() == Call.State.WAITING) {
1135             // Code for perform Call logging and missed call notification
1136             Connection c = ringingCall.getLatestConnection();
1137
1138             if (c != null) {
1139                 String number = c.getAddress();
1140                 int isPrivateNumber = c.getNumberPresentation();
1141                 long date = c.getCreateTime();
1142                 long duration = c.getDurationMillis();
1143                 int callLogType = mCallWaitingTimeOut ?
1144                         CallLog.Calls.MISSED_TYPE  : CallLog.Calls.INCOMING_TYPE;
1145
1146                 // get the callerinfo object and then log the call with it.
1147                 Object o = c.getUserData();
1148                 CallerInfo ci;
1149                 if ((o == null) || (o instanceof CallerInfo)) {
1150                     ci = (CallerInfo) o;
1151                 } else {
1152                     ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
1153                 }
1154
1155                 // Add Call log
1156                 Calls.addCall(ci, mApplication, number, isPrivateNumber, callLogType,
1157                         date, (int) duration / 1000);
1158
1159                 if (callLogType == CallLog.Calls.MISSED_TYPE) {
1160                     // Add missed call notification
1161                     NotificationMgr.getDefault().notifyMissedCall(ci.name,
1162                             ci.phoneNumber, ci.phoneLabel, date);
1163                 }
1164
1165                 // Set the Phone Call State to SINGLE_ACTIVE as there is only one connection
1166                 mApplication.cdmaPhoneCallState.setCurrentCallState(
1167                         CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
1168
1169                 // Hangup the RingingCall connection for CW
1170                 PhoneUtils.hangup(c);
1171             }
1172
1173             //Reset the mCallWaitingTimeOut boolean
1174             mCallWaitingTimeOut = false;
1175         }
1176     }
1177
1178     private void log(String msg) {
1179         Log.d(LOG_TAG, msg);
1180     }
1181 }