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