/
CallNotifier.java
executable file
·2124 lines (1885 loc) · 93.9 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.CallManager;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.TelephonyCapabilities;
import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
import com.android.internal.telephony.cdma.SignalToneUtil;
import android.app.ActivityManagerNative;
import android.content.Context;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.SystemVibrator;
import android.os.Vibrator;
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
/** The singleton instance. */
private static CallNotifier sInstance;
// 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 CALLWAITING_CALLERINFO_DISPLAY_DONE = 22;
private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 23;
private static final int DISPLAYINFO_NOTIFICATION_DONE = 24;
private static final int EVENT_OTA_PROVISION_CHANGE = 25;
private static final int CDMA_CALL_WAITING_REJECT = 26;
private static final int UPDATE_IN_CALL_NOTIFICATION = 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 CallManager mCM;
private Ringer mRinger;
private BluetoothHandsfree mBluetoothHandsfree;
private CallLogAsync mCallLog;
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;
/**
* Initialize the singleton CallNotifier instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
/* package */ static CallNotifier init(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
synchronized (CallNotifier.class) {
if (sInstance == null) {
sInstance = new CallNotifier(app, phone, ringer, btMgr, callLog);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
/** Private constructor; @see init() */
private CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
mApplication = app;
mCM = app.mCM;
mCallLog = callLog;
mAudioManager = (AudioManager) mApplication.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:
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:
onCustomRingtoneQueryTimeout((String) msg.obj);
break;
case PHONE_MWI_CHANGED:
onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
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.updateInCallScreen();
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:
if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
mApplication.handleOtaspEvent(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...");
mApplication.notificationMgr.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...");
mApplication.notificationMgr.updateInCallNotification();
}
break;
case PHONE_RINGBACK_TONE:
onRingbackTone((AsyncResult) msg.obj);
break;
case PHONE_RESEND_MUTE:
onResendMute();
break;
case UPDATE_IN_CALL_NOTIFICATION:
mApplication.notificationMgr.updateInCallNotification();
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);
}
};
/**
* Handles a "new ringing connection" event from the telephony layer.
*/
private void onNewRingingConnection(AsyncResult r) {
Connection c = (Connection) r.result;
log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
Call ringing = c.getCall();
Phone phone = ringing.getPhone();
// Check for a few cases where we totally ignore incoming calls.
if (ignoreAllIncomingCalls(phone)) {
// Immediately reject the call, without even indicating to the user
// that an incoming call occurred. (This will generally send the
// caller straight to voicemail, just as if we *had* shown the
// incoming-call UI and the user had declined the call.)
PhoneUtils.hangupRingingCall(ringing);
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!)
// via some future TelephonyManager API.
//
// See bug 1312336 for more details.
// 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)) {
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.");
}
/**
* Determines whether or not we're allowed to present incoming calls to the
* user, based on the capabilities and/or current state of the device.
*
* If this method returns true, that means we should immediately reject the
* current incoming call, without even indicating to the user that an
* incoming call occurred.
*
* (We only reject incoming calls in a few cases, like during an OTASP call
* when we can't interrupt the user, or if the device hasn't completed the
* SetupWizard yet. We also don't allow incoming calls on non-voice-capable
* devices. But note that we *always* allow incoming calls while in ECM.)
*
* @return true if we're *not* allowed to present an incoming call to
* the user.
*/
private boolean ignoreAllIncomingCalls(Phone phone) {
// Incoming calls are totally ignored on non-voice-capable devices.
if (!PhoneApp.sVoiceCapable) {
// ...but still log a warning, since we shouldn't have gotten this
// event in the first place! (Incoming calls *should* be blocked at
// the telephony layer on non-voice-capable capable devices.)
Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring...");
return true;
}
// In ECM (emergency callback mode), we ALWAYS allow incoming calls
// to get through to the user. (Note that ECM is applicable only to
// voice-capable CDMA devices).
if (PhoneUtils.isPhoneInEcm(phone)) {
if (DBG) log("Incoming call while in ECM: always allow...");
return false;
}
// Incoming calls are totally ignored if the device isn't provisioned yet.
boolean provisioned = Settings.Secure.getInt(mApplication.getContentResolver(),
Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
if (!provisioned) {
Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
return true;
}
// Incoming calls are totally ignored if an OTASP 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, "Ignoring incoming call: OTA call is active");
return true;
} else if (activateState || dialogState) {
// We *are* allowed to receive incoming calls at this point.
// But clear out any residual OTASP UI first.
// TODO: It's an MVC violation to twiddle the OTA UI state here;
// we should instead provide a higher-level API via OtaUtils.
if (dialogState) mApplication.dismissOtaDialogs();
mApplication.clearOtaState();
mApplication.clearInCallScreenMode();
return false;
}
}
// Normal case: allow this call to be presented to the user.
return false;
}
/**
* 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) {
// Reset the ringtone to the default first.
mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
// query the callerinfo to try to get the ringer.
PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
mApplication, 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.");
// Phone number (via getAddress()) is stored in the message to remember which
// number is actually used for the look up.
sendMessageDelayed(
Message.obtain(this, RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT, c.getAddress()),
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#updateNotificationAndLaunchIncomingCallUi()
*/
private void showIncomingCall() {
log("showIncomingCall()... phone state = " + mCM.getState());
// 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 *and* include the
// fullScreenIntent that'll launch the incoming-call UI.
// (This will usually take us straight to the incoming call
// screen, but if an immersive activity is running it'll just
// appear as a notification.)
if (DBG) log("- updating notification from showIncomingCall()...");
mApplication.notificationMgr.updateNotificationAndLaunchIncomingCallUi();
}
/**
* 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.
mApplication.notificationMgr.statusBarHelper
.enableNotificationAlerts(state == Phone.State.IDLE);
Phone fgPhone = mCM.getFgPhone();
if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if ((fgPhone.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 = fgPhone.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 phone state and other sensor/lock.
mApplication.updatePhoneState(state);
if (state == Phone.State.OFFHOOK) {
// stop call waiting tone if needed when answering
if (mCallWaitingTonePlayer != null) {
mCallWaitingTonePlayer.stopTone();
mCallWaitingTonePlayer = null;
}
if (VDBG) log("onPhoneStateChanged: OFF HOOK");
// make sure audio is in in-call mode now
PhoneUtils.setAudioMode(mCM);
// 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();
// Post a request to update the "in-call" status bar icon.
//
// We don't call NotificationMgr.updateInCallNotification()
// directly here, for two reasons:
// (1) a single phone state change might actually trigger multiple
// onPhoneStateChanged() callbacks, so this prevents redundant
// updates of the notification.
// (2) we suppress the status bar icon while the in-call UI is
// visible (see updateInCallNotification()). But when launching
// an outgoing call the phone actually goes OFFHOOK slightly
// *before* the InCallScreen comes up, so the delay here avoids a
// brief flicker of the icon at that point.
if (DBG) log("- posting UPDATE_IN_CALL_NOTIFICATION request...");
// Remove any previous requests in the queue
removeMessages(UPDATE_IN_CALL_NOTIFICATION);
final int IN_CALL_NOTIFICATION_UPDATE_DELAY = 1000; // msec
sendEmptyMessageDelayed(UPDATE_IN_CALL_NOTIFICATION,
IN_CALL_NOTIFICATION_UPDATE_DELAY);
}
if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
Connection c = fgPhone.getForegroundCall().getLatestConnection();
if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
mApplication))) {
if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
Call.State callState = fgPhone.getForegroundCall().getState();
if (mEmergencyTonePlayerVibrator == null) {
mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
}
if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
mIsEmergencyToneOn = Settings.System.getInt(
mApplication.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 ((fgPhone.getPhoneType() == Phone.PHONE_TYPE_GSM)
|| (fgPhone.getPhoneType() == Phone.PHONE_TYPE_SIP)) {
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);
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.
*/
@Override
public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
if (cookie instanceof Long) {
if (VDBG) log("CallerInfo query complete, posting missed call notification");
mApplication.notificationMgr.notifyMissedCall(ci.name, ci.phoneNumber,
ci.phoneLabel, ci.cachedPhoto, ci.cachedPhotoIcon,
((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(mCM.getFirstActiveRingingCall());
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();
}
}
}
/**
* Called when asynchronous CallerInfo query is taking too long (more than
* {@link #RINGTONE_QUERY_WAIT_TIME} msec), but we cannot wait any more.
*
* This looks up in-memory fallback cache and use it when available. If not, it just calls
* {@link #onCustomRingQueryComplete()} with default ringtone ("Send to voicemail" flag will
* be just ignored).
*
* @param number The phone number used for the async query. This method will take care of
* formatting or normalization of the number.
*/
private void onCustomRingtoneQueryTimeout(String number) {
// First of all, this case itself should be rare enough, though we cannot avoid it in
// some situations (e.g. IPC is slow due to system overload, database is in sync, etc.)
Log.w(LOG_TAG, "CallerInfo query took too long; look up local fallback cache.");
// This method is intentionally verbose for now to detect possible bad side-effect for it.
// TODO: Remove the verbose log when it looks stable and reliable enough.
final CallerInfoCache.CacheEntry entry =
mApplication.callerInfoCache.getCacheEntry(number);
if (entry != null) {
if (entry.sendToVoicemail) {
log("send to voicemail flag detected (in fallback cache). hanging up.");
PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());