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