/
CallNotifier.java
executable file
·2011 lines (1781 loc) · 88.3 KB
/
CallNotifier.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.phone;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
import com.android.internal.telephony.cdma.SignalToneUtil;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.CallManager;
import android.app.ActivityManagerNative;
import android.content.Context;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Vibrator;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
/**
* Phone app module that listens for phone state changes and various other
* events from the telephony layer, and triggers any resulting UI behavior
* (like starting the Ringer and Incoming Call UI, playing in-call tones,
* updating notifications, writing call log entries, etc.)
*/
public class CallNotifier extends Handler
implements CallerInfoAsyncQuery.OnQueryCompleteListener {
private static final String LOG_TAG = "CallNotifier";
private static final boolean DBG =
(PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
// Maximum time we allow the CallerInfo query to run,
// before giving up and falling back to the default ringtone.
private static final int RINGTONE_QUERY_WAIT_TIME = 500; // msec
// Timers related to CDMA Call Waiting
// 1) For displaying Caller Info
// 2) For disabling "Add Call" menu option once User selects Ignore or CW Timeout occures
private static final int CALLWAITING_CALLERINFO_DISPLAY_TIME = 20000; // msec
private static final int CALLWAITING_ADDCALL_DISABLE_TIME = 30000; // msec
// Time to display the DisplayInfo Record sent by CDMA network
private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
// Boolean to keep track of whether or not a CDMA Call Waiting call timed out.
//
// This is CDMA-specific, because with CDMA we *don't* get explicit
// notification from the telephony layer that a call-waiting call has
// stopped ringing. Instead, when a call-waiting call first comes in we
// start a 20-second timer (see CALLWAITING_CALLERINFO_DISPLAY_DONE), and
// if the timer expires we clean up the call and treat it as a missed call.
//
// If this field is true, that means that the current Call Waiting call
// "timed out" and should be logged in Call Log as a missed call. If it's
// false when we reach onCdmaCallWaitingReject(), we can assume the user
// explicitly rejected this call-waiting call.
//
// This field is reset to false any time a call-waiting call first comes
// in, and after cleaning up a missed call-waiting call. It's only ever
// set to true when the CALLWAITING_CALLERINFO_DISPLAY_DONE timer fires.
//
// TODO: do we really need a member variable for this? Don't we always
// know at the moment we call onCdmaCallWaitingReject() whether this is an
// explicit rejection or not?
// (Specifically: when we call onCdmaCallWaitingReject() from
// PhoneUtils.hangupRingingCall() that means the user deliberately rejected
// the call, and if we call onCdmaCallWaitingReject() because of a
// CALLWAITING_CALLERINFO_DISPLAY_DONE event that means that it timed
// out...)
private boolean mCallWaitingTimeOut = false;
// values used to track the query state
private static final int CALLERINFO_QUERY_READY = 0;
private static final int CALLERINFO_QUERYING = -1;
// the state of the CallerInfo Query.
private int mCallerInfoQueryState;
// object used to synchronize access to mCallerInfoQueryState
private Object mCallerInfoQueryStateGuard = new Object();
// Event used to indicate a query timeout.
private static final int RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT = 100;
// Events from the Phone object:
private static final int PHONE_STATE_CHANGED = 1;
private static final int PHONE_NEW_RINGING_CONNECTION = 2;
private static final int PHONE_DISCONNECT = 3;
private static final int PHONE_UNKNOWN_CONNECTION_APPEARED = 4;
private static final int PHONE_INCOMING_RING = 5;
private static final int PHONE_STATE_DISPLAYINFO = 6;
private static final int PHONE_STATE_SIGNALINFO = 7;
private static final int PHONE_CDMA_CALL_WAITING = 8;
private static final int PHONE_ENHANCED_VP_ON = 9;
private static final int PHONE_ENHANCED_VP_OFF = 10;
private static final int PHONE_RINGBACK_TONE = 11;
private static final int PHONE_RESEND_MUTE = 12;
// Events generated internally:
private static final int PHONE_MWI_CHANGED = 21;
private static final int PHONE_BATTERY_LOW = 22;
private static final int CALLWAITING_CALLERINFO_DISPLAY_DONE = 23;
private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 24;
private static final int DISPLAYINFO_NOTIFICATION_DONE = 25;
private static final int EVENT_OTA_PROVISION_CHANGE = 26;
private static final int CDMA_CALL_WAITING_REJECT = 27;
// Emergency call related defines:
private static final int EMERGENCY_TONE_OFF = 0;
private static final int EMERGENCY_TONE_ALERT = 1;
private static final int EMERGENCY_TONE_VIBRATE = 2;
private PhoneApp mApplication;
private Phone mPhone;
private CallManager mCM;
private Ringer mRinger;
private BluetoothHandsfree mBluetoothHandsfree;
private CallLogAsync mCallLog;
// TODO get rid of mPhoneContext and replace it with mApplcation's
private Context mPhoneContext;
private boolean mSilentRingerRequested;
// ToneGenerator instance for playing SignalInfo tones
private ToneGenerator mSignalInfoToneGenerator;
// The tone volume relative to other sounds in the stream SignalInfo
private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80;
private Call.State mPreviousCdmaCallState;
private boolean mVoicePrivacyState = false;
private boolean mIsCdmaRedialCall = false;
// Emergency call tone and vibrate:
private int mIsEmergencyToneOn;
private int mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
private EmergencyTonePlayerVibrator mEmergencyTonePlayerVibrator;
// Ringback tone player
private InCallTonePlayer mInCallRingbackTonePlayer;
// Call waiting tone player
private InCallTonePlayer mCallWaitingTonePlayer;
// Cached AudioManager
private AudioManager mAudioManager;
public CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
mApplication = app;
mPhone = phone;
mCM = app.mCM;
mCallLog = callLog;
// TODO
//replace this with mApplication context while finally get rid of mPhone
mPhoneContext = mPhone.getContext();
mAudioManager = (AudioManager) mPhoneContext.getSystemService(Context.AUDIO_SERVICE);
registerForNotifications();
// Instantiate the ToneGenerator for SignalInfo and CallWaiting
// TODO: We probably don't need the mSignalInfoToneGenerator instance
// around forever. Need to change it so as to create a ToneGenerator instance only
// when a tone is being played and releases it after its done playing.
try {
mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
TONE_RELATIVE_VOLUME_SIGNALINFO);
} catch (RuntimeException e) {
Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
"mSignalInfoToneGenerator: " + e);
mSignalInfoToneGenerator = null;
}
mRinger = ringer;
mBluetoothHandsfree = btMgr;
TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
| PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PHONE_NEW_RINGING_CONNECTION:
if (DBG) log("RINGING... (new)");
onNewRingingConnection((AsyncResult) msg.obj);
mSilentRingerRequested = false;
break;
case PHONE_INCOMING_RING:
// repeat the ring when requested by the RIL, and when the user has NOT
// specifically requested silence.
if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
PhoneBase pb = (PhoneBase)((AsyncResult)msg.obj).result;
if ((pb.getState() == Phone.State.RINGING)
&& (mSilentRingerRequested == false)) {
if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
mRinger.ring();
} else {
if (DBG) log("RING before NEW_RING, skipping");
}
}
break;
case PHONE_STATE_CHANGED:
onPhoneStateChanged((AsyncResult) msg.obj);
break;
case PHONE_DISCONNECT:
if (DBG) log("DISCONNECT");
onDisconnect((AsyncResult) msg.obj);
break;
case PHONE_UNKNOWN_CONNECTION_APPEARED:
onUnknownConnectionAppeared((AsyncResult) msg.obj);
break;
case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
// CallerInfo query is taking too long! But we can't wait
// any more, so start ringing NOW even if it means we won't
// use the correct custom ringtone.
Log.w(LOG_TAG, "CallerInfo query took too long; manually starting ringer");
// In this case we call onCustomRingQueryComplete(), just
// like if the query had completed normally. (But we're
// going to get the default ringtone, since we never got
// the chance to call Ringer.setCustomRingtoneUri()).
onCustomRingQueryComplete();
break;
case PHONE_MWI_CHANGED:
onMwiChanged(mPhone.getMessageWaitingIndicator());
break;
case PHONE_BATTERY_LOW:
onBatteryLow();
break;
case PHONE_CDMA_CALL_WAITING:
if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
onCdmaCallWaiting((AsyncResult) msg.obj);
break;
case CDMA_CALL_WAITING_REJECT:
Log.i(LOG_TAG, "Received CDMA_CALL_WAITING_REJECT event");
onCdmaCallWaitingReject();
break;
case CALLWAITING_CALLERINFO_DISPLAY_DONE:
Log.i(LOG_TAG, "Received CALLWAITING_CALLERINFO_DISPLAY_DONE event");
mCallWaitingTimeOut = true;
onCdmaCallWaitingReject();
break;
case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
// Set the mAddCallMenuStateAfterCW state to true
mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
mApplication.updateInCallScreenTouchUi();
break;
case PHONE_STATE_DISPLAYINFO:
if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
onDisplayInfo((AsyncResult) msg.obj);
break;
case PHONE_STATE_SIGNALINFO:
if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
onSignalInfo((AsyncResult) msg.obj);
break;
case DISPLAYINFO_NOTIFICATION_DONE:
if (DBG) log("Received Display Info notification done event ...");
CdmaDisplayInfo.dismissDisplayInfoRecord();
break;
case EVENT_OTA_PROVISION_CHANGE:
mApplication.handleOtaEvents(msg);
break;
case PHONE_ENHANCED_VP_ON:
if (DBG) log("PHONE_ENHANCED_VP_ON...");
if (!mVoicePrivacyState) {
int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
new InCallTonePlayer(toneToPlay).start();
mVoicePrivacyState = true;
// Update the VP icon:
if (DBG) log("- updating notification for VP state...");
NotificationMgr.getDefault().updateInCallNotification();
}
break;
case PHONE_ENHANCED_VP_OFF:
if (DBG) log("PHONE_ENHANCED_VP_OFF...");
if (mVoicePrivacyState) {
int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
new InCallTonePlayer(toneToPlay).start();
mVoicePrivacyState = false;
// Update the VP icon:
if (DBG) log("- updating notification for VP state...");
NotificationMgr.getDefault().updateInCallNotification();
}
break;
case PHONE_RINGBACK_TONE:
onRingbackTone((AsyncResult) msg.obj);
break;
case PHONE_RESEND_MUTE:
onResendMute();
break;
default:
// super.handleMessage(msg);
}
}
PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onMessageWaitingIndicatorChanged(boolean mwi) {
onMwiChanged(mwi);
}
@Override
public void onCallForwardingIndicatorChanged(boolean cfi) {
onCfiChanged(cfi);
}
};
private void onNewRingingConnection(AsyncResult r) {
Connection c = (Connection) r.result;
if (DBG) log("onNewRingingConnection(): " + c);
Call ringing = c.getCall();
Phone phone = ringing.getPhone();
// Incoming calls are totally ignored if the device isn't provisioned yet
boolean provisioned = Settings.Secure.getInt(mPhoneContext.getContentResolver(),
Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
if (!provisioned && !PhoneUtils.isPhoneInEcm(mPhone)) {
Log.i(LOG_TAG, "CallNotifier: rejecting incoming call: not provisioned / ECM");
// Send the caller straight to voicemail, just like
// "rejecting" an incoming call.
PhoneUtils.hangupRingingCall(ringing);
return;
}
// Incoming calls are totally ignored if OTA call is active
if (TelephonyCapabilities.supportsOtasp(phone)) {
boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState
== OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState
== OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG);
boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState;
if (spcState) {
Log.i(LOG_TAG, "CallNotifier: rejecting incoming call: OTA call is active");
PhoneUtils.hangupRingingCall(ringing);
return;
} else if (activateState || dialogState) {
if (dialogState) mApplication.dismissOtaDialogs();
mApplication.clearOtaState();
mApplication.clearInCallScreenMode();
}
}
if (c == null) {
Log.w(LOG_TAG, "CallNotifier.onNewRingingConnection(): null connection!");
// Should never happen, but if it does just bail out and do nothing.
return;
}
if (!c.isRinging()) {
Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
// This is a very strange case: an incoming call that stopped
// ringing almost instantly after the onNewRingingConnection()
// event. There's nothing we can do here, so just bail out
// without doing anything. (But presumably we'll log it in
// the call log when the disconnect event comes in...)
return;
}
// Stop any signalInfo tone being played on receiving a Call
stopSignalInfoTone();
Call.State state = c.getState();
// State will be either INCOMING or WAITING.
if (VDBG) log("- connection is ringing! state = " + state);
// if (DBG) PhoneUtils.dumpCallState(mPhone);
// No need to do any service state checks here (like for
// "emergency mode"), since in those states the SIM won't let
// us get incoming connections in the first place.
// TODO: Consider sending out a serialized broadcast Intent here
// (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
// ringer and going to the in-call UI. The intent should contain
// the caller-id info for the current connection, and say whether
// it would be a "call waiting" call or a regular ringing call.
// If anybody consumed the broadcast, we'd bail out without
// ringing or bringing up the in-call UI.
//
// This would give 3rd party apps a chance to listen for (and
// intercept) new ringing connections. An app could reject the
// incoming call by consuming the broadcast and doing nothing, or
// it could "pick up" the call (without any action by the user!)
// by firing off an ACTION_ANSWER intent.
//
// We'd need to protect this with a new "intercept incoming calls"
// system permission.
// Obtain a partial wake lock to make sure the CPU doesn't go to
// sleep before we finish bringing up the InCallScreen.
// (This will be upgraded soon to a full wake lock; see
// showIncomingCall().)
if (VDBG) log("Holding wake lock on new incoming connection.");
mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL);
// - don't ring for call waiting connections
// - do this before showing the incoming call panel
if (PhoneUtils.isRealIncomingCall(state)) {
PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_RINGING);
startIncomingCallQuery(c);
} else {
if (VDBG) log("- starting call waiting tone...");
if (mCallWaitingTonePlayer == null) {
mCallWaitingTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING);
mCallWaitingTonePlayer.start();
}
// in this case, just fall through like before, and call
// showIncomingCall().
if (DBG) log("- showing incoming call (this is a WAITING call)...");
showIncomingCall();
}
// Note we *don't* post a status bar notification here, since
// we're not necessarily ready to actually show the incoming call
// to the user. (For calls in the INCOMING state, at least, we
// still need to run a caller-id query, and we may not even ring
// at all if the "send directly to voicemail" flag is set.)
//
// Instead, we update the notification (and potentially launch the
// InCallScreen) from the showIncomingCall() method, which runs
// when the caller-id query completes or times out.
if (VDBG) log("- onNewRingingConnection() done.");
}
/**
* Helper method to manage the start of incoming call queries
*/
private void startIncomingCallQuery(Connection c) {
// TODO: cache the custom ringer object so that subsequent
// calls will not need to do this query work. We can keep
// the MRU ringtones in memory. We'll still need to hit
// the database to get the callerinfo to act as a key,
// but at least we can save the time required for the
// Media player setup. The only issue with this is that
// we may need to keep an eye on the resources the Media
// player uses to keep these ringtones around.
// make sure we're in a state where we can be ready to
// query a ringtone uri.
boolean shouldStartQuery = false;
synchronized (mCallerInfoQueryStateGuard) {
if (mCallerInfoQueryState == CALLERINFO_QUERY_READY) {
mCallerInfoQueryState = CALLERINFO_QUERYING;
shouldStartQuery = true;
}
}
if (shouldStartQuery) {
// create a custom ringer using the default ringer first
mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
// query the callerinfo to try to get the ringer.
PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
mPhoneContext, c, this, this);
// if this has already been queried then just ring, otherwise
// we wait for the alloted time before ringing.
if (cit.isFinal) {
if (VDBG) log("- CallerInfo already up to date, using available data");
onQueryComplete(0, this, cit.currentInfo);
} else {
if (VDBG) log("- Starting query, posting timeout message.");
sendEmptyMessageDelayed(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT,
RINGTONE_QUERY_WAIT_TIME);
}
// The call to showIncomingCall() will happen after the
// queries are complete (or time out).
} else {
// This should never happen; its the case where an incoming call
// arrives at the same time that the query is still being run,
// and before the timeout window has closed.
EventLog.writeEvent(EventLogTags.PHONE_UI_MULTIPLE_QUERY);
// In this case, just log the request and ring.
if (VDBG) log("RINGING... (request to ring arrived while query is running)");
mRinger.ring();
// in this case, just fall through like before, and call
// showIncomingCall().
if (DBG) log("- showing incoming call (couldn't start query)...");
showIncomingCall();
}
}
/**
* Performs the final steps of the onNewRingingConnection sequence:
* starts the ringer, and brings up the "incoming call" UI.
*
* Normally, this is called when the CallerInfo query completes (see
* onQueryComplete()). In this case, onQueryComplete() has already
* configured the Ringer object to use the custom ringtone (if there
* is one) for this caller. So we just tell the Ringer to start, and
* proceed to the InCallScreen.
*
* But this method can *also* be called if the
* RINGTONE_QUERY_WAIT_TIME timeout expires, which means that the
* CallerInfo query is taking too long. In that case, we log a
* warning but otherwise we behave the same as in the normal case.
* (We still tell the Ringer to start, but it's going to use the
* default ringtone.)
*/
private void onCustomRingQueryComplete() {
boolean isQueryExecutionTimeExpired = false;
synchronized (mCallerInfoQueryStateGuard) {
if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
mCallerInfoQueryState = CALLERINFO_QUERY_READY;
isQueryExecutionTimeExpired = true;
}
}
if (isQueryExecutionTimeExpired) {
// There may be a problem with the query here, since the
// default ringtone is playing instead of the custom one.
Log.w(LOG_TAG, "CallerInfo query took too long; falling back to default ringtone");
EventLog.writeEvent(EventLogTags.PHONE_UI_RINGER_QUERY_ELAPSED);
}
// Make sure we still have an incoming call!
//
// (It's possible for the incoming call to have been disconnected
// while we were running the query. In that case we better not
// start the ringer here, since there won't be any future
// DISCONNECT event to stop it!)
//
// Note we don't have to worry about the incoming call going away
// *after* this check but before we call mRinger.ring() below,
// since in that case we *will* still get a DISCONNECT message sent
// to our handler. (And we will correctly stop the ringer when we
// process that event.)
if (mCM.getState() != Phone.State.RINGING) {
Log.i(LOG_TAG, "onCustomRingQueryComplete: No incoming call! Bailing out...");
// Don't start the ringer *or* bring up the "incoming call" UI.
// Just bail out.
return;
}
// Ring, either with the queried ringtone or default one.
if (VDBG) log("RINGING... (onCustomRingQueryComplete)");
mRinger.ring();
// ...and display the incoming call to the user:
if (DBG) log("- showing incoming call (custom ring query complete)...");
showIncomingCall();
}
private void onUnknownConnectionAppeared(AsyncResult r) {
Phone.State state = mCM.getState();
if (state == Phone.State.OFFHOOK) {
// basically do onPhoneStateChanged + display the incoming call UI
onPhoneStateChanged(r);
if (DBG) log("- showing incoming call (unknown connection appeared)...");
showIncomingCall();
}
}
/**
* Informs the user about a new incoming call.
*
* In most cases this means "bring up the full-screen incoming call
* UI". However, if an immersive activity is running, the system
* NotificationManager will instead pop up a small notification window
* on top of the activity.
*
* Watch out: be sure to call this method only once per incoming call,
* or otherwise we may end up launching the InCallScreen multiple
* times (which can lead to slow responsiveness and/or visible
* glitches.)
*
* Note this method handles only the onscreen UI for incoming calls;
* the ringer and/or vibrator are started separately (see the various
* calls to Ringer.ring() in this class.)
*
* @see NotificationMgr.updateInCallNotification()
*/
private void showIncomingCall() {
if (DBG) log("showIncomingCall()...");
// Before bringing up the "incoming call" UI, force any system
// dialogs (like "recent tasks" or the power dialog) to close first.
try {
ActivityManagerNative.getDefault().closeSystemDialogs("call");
} catch (RemoteException e) {
}
// Go directly to the in-call screen.
// (No need to do anything special if we're already on the in-call
// screen; it'll notice the phone state change and update itself.)
// But first, grab a full wake lock. We do this here, before we
// even fire off the InCallScreen intent, to make sure the
// ActivityManager doesn't try to pause the InCallScreen as soon
// as it comes up. (See bug 1648751.)
//
// And since the InCallScreen isn't visible yet (we haven't even
// fired off the intent yet), we DON'T want the screen to actually
// come on right now. So *before* acquiring the wake lock we need
// to call preventScreenOn(), which tells the PowerManager that
// the screen should stay off even if someone's holding a full
// wake lock. (This prevents any flicker during the "incoming
// call" sequence. The corresponding preventScreenOn(false) call
// will come from the InCallScreen when it's finally ready to be
// displayed.)
//
// TODO: this is all a temporary workaround. The real fix is to add
// an Activity attribute saying "this Activity wants to wake up the
// phone when it's displayed"; that way the ActivityManager could
// manage the wake locks *and* arrange for the screen to come on at
// the exact moment that the InCallScreen is ready to be displayed.
// (See bug 1648751.)
//
// TODO: also, we should probably *not* do any of this if the
// screen is already on(!)
mApplication.preventScreenOn(true);
mApplication.requestWakeState(PhoneApp.WakeState.FULL);
// Post the "incoming call" notification. This will usually take
// us straight to the incoming call screen (thanks to the
// notification's "fullScreenIntent" field), but if an immersive
// activity is running it'll just appear as a notification.
if (DBG) log("- updating notification from showIncomingCall()...");
NotificationMgr.getDefault().updateInCallNotification();
}
/**
* Updates the phone UI in response to phone state changes.
*
* Watch out: certain state changes are actually handled by their own
* specific methods:
* - see onNewRingingConnection() for new incoming calls
* - see onDisconnect() for calls being hung up or disconnected
*/
private void onPhoneStateChanged(AsyncResult r) {
Phone.State state = mCM.getState();
if (VDBG) log("onPhoneStateChanged: state = " + state);
// Turn status bar notifications on or off depending upon the state
// of the phone. Notification Alerts (audible or vibrating) should
// be on if and only if the phone is IDLE.
NotificationMgr.getDefault().getStatusBarMgr()
.enableNotificationAlerts(state == Phone.State.IDLE);
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if ((mPhone.getForegroundCall().getState() == Call.State.ACTIVE)
&& ((mPreviousCdmaCallState == Call.State.DIALING)
|| (mPreviousCdmaCallState == Call.State.ALERTING))) {
if (mIsCdmaRedialCall) {
int toneToPlay = InCallTonePlayer.TONE_REDIAL;
new InCallTonePlayer(toneToPlay).start();
}
// Stop any signal info tone when call moves to ACTIVE state
stopSignalInfoTone();
}
mPreviousCdmaCallState = mPhone.getForegroundCall().getState();
}
// Have the PhoneApp recompute its mShowBluetoothIndication
// flag based on the (new) telephony state.
// There's no need to force a UI update since we update the
// in-call notification ourselves (below), and the InCallScreen
// listens for phone state changes itself.
mApplication.updateBluetoothIndication(false);
// Update the proximity sensor mode (on devices that have a
// proximity sensor).
mApplication.updatePhoneState(state);
if (state == Phone.State.OFFHOOK) {
// stop call waiting tone if needed when answering
if (mCallWaitingTonePlayer != null) {
mCallWaitingTonePlayer.stopTone();
mCallWaitingTonePlayer = null;
}
PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
if (VDBG) log("onPhoneStateChanged: OFF HOOK");
// If Audio Mode is not In Call, then set the Audio Mode. This
// changes is needed because for one of the carrier specific test case,
// call is originated from the lower layer without using the UI, and
// since calling does not go through DIALING state, it skips the steps
// of setting the Audio Mode
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if (mAudioManager.getMode() != AudioManager.MODE_IN_CALL) {
PhoneUtils.setAudioMode(mPhoneContext, AudioManager.MODE_IN_CALL);
}
}
// if the call screen is showing, let it handle the event,
// otherwise handle it here.
if (!mApplication.isShowingCallScreen()) {
mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
}
// Since we're now in-call, the Ringer should definitely *not*
// be ringing any more. (This is just a sanity-check; we
// already stopped the ringer explicitly back in
// PhoneUtils.answerCall(), before the call to phone.acceptCall().)
// TODO: Confirm that this call really *is* unnecessary, and if so,
// remove it!
if (DBG) log("stopRing()... (OFFHOOK state)");
mRinger.stopRing();
// put a icon in the status bar
if (DBG) log("- updating notification for phone state change...");
NotificationMgr.getDefault().updateInCallNotification();
}
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
Connection c = mPhone.getForegroundCall().getLatestConnection();
if ((c != null) && (PhoneNumberUtils.isEmergencyNumber(c.getAddress()))) {
if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
Call.State callState = mPhone.getForegroundCall().getState();
if (mEmergencyTonePlayerVibrator == null) {
mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
}
if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
mIsEmergencyToneOn = Settings.System.getInt(
mPhoneContext.getContentResolver(),
Settings.System.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
if (mEmergencyTonePlayerVibrator != null) {
mEmergencyTonePlayerVibrator.start();
}
}
} else if (callState == Call.State.ACTIVE) {
if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) {
if (mEmergencyTonePlayerVibrator != null) {
mEmergencyTonePlayerVibrator.stop();
}
}
}
}
}
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_GSM) {
Call.State callState = mCM.getActiveFgCallState();
if (!callState.isDialing()) {
// If call get activated or disconnected before the ringback
// tone stops, we have to stop it to prevent disturbing.
if (mInCallRingbackTonePlayer != null) {
mInCallRingbackTonePlayer.stopTone();
mInCallRingbackTonePlayer = null;
}
}
}
}
void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
// Unregister all events from the old obsolete phone
mCM.unregisterForNewRingingConnection(this);
mCM.unregisterForPreciseCallStateChanged(this);
mCM.unregisterForDisconnect(this);
mCM.unregisterForUnknownConnection(this);
mCM.unregisterForIncomingRing(this);
mCM.unregisterForCallWaiting(this);
mCM.unregisterForDisplayInfo(this);
mCM.unregisterForSignalInfo(this);
mCM.unregisterForCdmaOtaStatusChange(this);
mCM.unregisterForRingbackTone(this);
mCM.unregisterForResendIncallMute(this);
// Release the ToneGenerator used for playing SignalInfo and CallWaiting
if (mSignalInfoToneGenerator != null) {
mSignalInfoToneGenerator.release();
}
// Clear ringback tone player
mInCallRingbackTonePlayer = null;
// Clear call waiting tone player
mCallWaitingTonePlayer = null;
mCM.unregisterForInCallVoicePrivacyOn(this);
mCM.unregisterForInCallVoicePrivacyOff(this);
// Register all events new to the new active phone
registerForNotifications();
}
private void registerForNotifications() {
mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
mCM.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
mCM.registerForDisconnect(this, PHONE_DISCONNECT, null);
mCM.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
mCM.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
if (TelephonyCapabilities.supportsOtasp(mPhone)) {
mCM.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
}
mCM.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
mCM.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
mCM.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
mCM.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
mCM.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
mCM.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
mCM.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
}
/**
* Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
* refreshes the CallCard data when it called. If called with this
* class itself, it is assumed that we have been waiting for the ringtone
* and direct to voicemail settings to update.
*/
public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
if (cookie instanceof Long) {
if (VDBG) log("CallerInfo query complete, posting missed call notification");
NotificationMgr.getDefault().notifyMissedCall(ci.name, ci.phoneNumber,
ci.phoneLabel, ((Long) cookie).longValue());
} else if (cookie instanceof CallNotifier) {
if (VDBG) log("CallerInfo query complete (for CallNotifier), "
+ "updating state for incoming call..");
// get rid of the timeout messages
removeMessages(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT);
boolean isQueryExecutionTimeOK = false;
synchronized (mCallerInfoQueryStateGuard) {
if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
mCallerInfoQueryState = CALLERINFO_QUERY_READY;
isQueryExecutionTimeOK = true;
}
}
//if we're in the right state
if (isQueryExecutionTimeOK) {
// send directly to voicemail.
if (ci.shouldSendToVoicemail) {
if (DBG) log("send to voicemail flag detected. hanging up.");
PhoneUtils.hangupRingingCall(mPhone.getRingingCall());
return;
}
// set the ringtone uri to prepare for the ring.
if (ci.contactRingtoneUri != null) {
if (DBG) log("custom ringtone found, setting up ringer.");
Ringer r = ((CallNotifier) cookie).mRinger;
r.setCustomRingtoneUri(ci.contactRingtoneUri);
}
// ring, and other post-ring actions.
onCustomRingQueryComplete();
}
}
}
private void onDisconnect(AsyncResult r) {
if (VDBG) log("onDisconnect()... CallManager state: " + mCM.getState());
mVoicePrivacyState = false;
int autoretrySetting = 0;
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
autoretrySetting = android.provider.Settings.System.getInt(mPhoneContext.
getContentResolver(),android.provider.Settings.System.CALL_AUTO_RETRY, 0);
}
if (mCM.getState() == Phone.State.IDLE) {
PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
}
// Stop any signalInfo tone being played when a call gets ended
stopSignalInfoTone();
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
// Resetting the CdmaPhoneCallState members
mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
// Remove Call waiting timers
removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
}
Connection c = (Connection) r.result;
if (DBG && c != null) {
log("- onDisconnect: cause = " + c.getDisconnectCause()
+ ", incoming = " + c.isIncoming()
+ ", date = " + c.getCreateTime());
}
// Stop the ringer if it was ringing (for an incoming call that
// either disconnected by itself, or was rejected by the user.)
//
// TODO: We technically *shouldn't* stop the ringer if the
// foreground or background call disconnects while an incoming call
// is still ringing, but that's a really rare corner case.
// It's safest to just unconditionally stop the ringer here.
// CDMA: For Call collision cases i.e. when the user makes an out going call
// and at the same time receives an Incoming Call, the Incoming Call is given
// higher preference. At this time framework sends a disconnect for the Out going
// call connection hence we should *not* be stopping the ringer being played for
// the Incoming Call
if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if (PhoneUtils.isRealIncomingCall(mPhone.getRingingCall().getState())) {
// Also we need to take off the "In Call" icon from the Notification
// area as the Out going Call never got connected
if (DBG) log("cancelCallInProgressNotification()... (onDisconnect)");
NotificationMgr.getDefault().cancelCallInProgressNotification();
} else {
if (DBG) log("stopRing()... (onDisconnect)");
mRinger.stopRing();
}
} else { // GSM
if (DBG) log("stopRing()... (onDisconnect)");
mRinger.stopRing();
}
// stop call waiting tone if needed when disconnecting
if (mCallWaitingTonePlayer != null) {
mCallWaitingTonePlayer.stopTone();
mCallWaitingTonePlayer = null;
}
// If this is the end of an OTASP call, pass it on to the PhoneApp.
if (c != null && TelephonyCapabilities.supportsOtasp(mPhone)) {
final String number = c.getAddress();
if (mPhone.isOtaSpNumber(number)) {
if (DBG) log("onDisconnect: this was an OTASP call!");
mApplication.handleOtaspDisconnect();
}
}
// Check for the various tones we might need to play (thru the
// earpiece) after a call disconnects.
int toneToPlay = InCallTonePlayer.TONE_NONE;
// The "Busy" or "Congestion" tone is the highest priority:
if (c != null) {
Connection.DisconnectCause cause = c.getDisconnectCause();
if (cause == Connection.DisconnectCause.BUSY) {
if (DBG) log("- need to play BUSY tone!");
toneToPlay = InCallTonePlayer.TONE_BUSY;
} else if (cause == Connection.DisconnectCause.CONGESTION) {
if (DBG) log("- need to play CONGESTION tone!");
toneToPlay = InCallTonePlayer.TONE_CONGESTION;
} else if (((cause == Connection.DisconnectCause.NORMAL)