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.CallManager;
21 import com.android.internal.telephony.CallerInfo;
22 import com.android.internal.telephony.CallerInfoAsyncQuery;
23 import com.android.internal.telephony.Connection;
24 import com.android.internal.telephony.Phone;
25 import com.android.internal.telephony.PhoneConstants;
26 import com.android.internal.telephony.PhoneBase;
27 import com.android.internal.telephony.TelephonyCapabilities;
28 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
29 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
30 import com.android.internal.telephony.cdma.SignalToneUtil;
31 
32 import android.app.ActivityManagerNative;
33 import android.bluetooth.BluetoothAdapter;
34 import android.bluetooth.BluetoothHeadset;
35 import android.bluetooth.BluetoothProfile;
36 import android.content.Context;
37 import android.media.AudioAttributes;
38 import android.media.AudioManager;
39 import android.media.ToneGenerator;
40 import android.net.Uri;
41 import android.os.AsyncResult;
42 import android.os.Handler;
43 import android.os.Message;
44 import android.os.SystemProperties;
45 import android.os.SystemVibrator;
46 import android.os.Vibrator;
47 import android.provider.CallLog.Calls;
48 import android.provider.Settings;
49 import android.telecom.TelecomManager;
50 import android.telephony.DisconnectCause;
51 import android.telephony.PhoneNumberUtils;
52 import android.telephony.PhoneStateListener;
53 import android.telephony.SubscriptionInfo;
54 import android.telephony.SubscriptionManager;
55 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
56 import android.telephony.TelephonyManager;
57 import android.util.ArrayMap;
58 import android.util.EventLog;
59 import android.util.Log;
60 
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Map;
64 
65 /**
66  * Phone app module that listens for phone state changes and various other
67  * events from the telephony layer, and triggers any resulting UI behavior
68  * (like starting the Incoming Call UI, playing in-call tones,
69  * updating notifications, writing call log entries, etc.)
70  */
71 public class CallNotifier extends Handler {
72     private static final String LOG_TAG = "CallNotifier";
73     private static final boolean DBG =
74             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
75     private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
76 
77     // Time to display the message from the underlying phone layers.
78     private static final int SHOW_MESSAGE_NOTIFICATION_TIME = 3000; // msec
79 
80     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
81             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
82             .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
83             .build();
84 
85     /** The singleton instance. */
86     private static CallNotifier sInstance;
87 
88     // values used to track the query state
89     private static final int CALLERINFO_QUERY_READY = 0;
90     private static final int CALLERINFO_QUERYING = -1;
91 
92     // the state of the CallerInfo Query.
93     private int mCallerInfoQueryState;
94 
95     // object used to synchronize access to mCallerInfoQueryState
96     private Object mCallerInfoQueryStateGuard = new Object();
97     private Map<Integer, CallNotifierPhoneStateListener> mPhoneStateListeners =
98             new ArrayMap<Integer, CallNotifierPhoneStateListener>();
99 
100     private PhoneGlobals mApplication;
101     private CallManager mCM;
102     private BluetoothHeadset mBluetoothHeadset;
103     private CallLogger mCallLogger;
104 
105     // ToneGenerator instance for playing SignalInfo tones
106     private ToneGenerator mSignalInfoToneGenerator;
107 
108     // The tone volume relative to other sounds in the stream SignalInfo
109     private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80;
110 
111     private Call.State mPreviousCdmaCallState;
112     private boolean mVoicePrivacyState = false;
113     private boolean mIsCdmaRedialCall = false;
114 
115     // Cached AudioManager
116     private AudioManager mAudioManager;
117     private final BluetoothManager mBluetoothManager;
118     private SubscriptionManager mSubscriptionManager;
119     private TelephonyManager mTelephonyManager;
120 
121     /**
122      * Initialize the singleton CallNotifier instance.
123      * This is only done once, at startup, from PhoneApp.onCreate().
124      */
init( PhoneGlobals app, CallLogger callLogger, CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager)125     /* package */ static CallNotifier init(
126             PhoneGlobals app,
127             CallLogger callLogger,
128             CallStateMonitor callStateMonitor,
129             BluetoothManager bluetoothManager) {
130         synchronized (CallNotifier.class) {
131             if (sInstance == null) {
132                 sInstance = new CallNotifier(app, callLogger, callStateMonitor, bluetoothManager);
133             } else {
134                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
135             }
136             return sInstance;
137         }
138     }
139 
140     /** Private constructor; @see init() */
CallNotifier( PhoneGlobals app, CallLogger callLogger, CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager)141     private CallNotifier(
142             PhoneGlobals app,
143             CallLogger callLogger,
144             CallStateMonitor callStateMonitor,
145             BluetoothManager bluetoothManager) {
146         mApplication = app;
147         mCM = app.mCM;
148         mCallLogger = callLogger;
149         mBluetoothManager = bluetoothManager;
150 
151         mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
152         mTelephonyManager =
153                 (TelephonyManager) mApplication.getSystemService(Context.TELEPHONY_SERVICE);
154         mSubscriptionManager = (SubscriptionManager) mApplication.getSystemService(
155                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
156 
157         callStateMonitor.addListener(this);
158 
159         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
160         if (adapter != null) {
161             adapter.getProfileProxy(mApplication.getApplicationContext(),
162                     mBluetoothProfileServiceListener,
163                     BluetoothProfile.HEADSET);
164         }
165 
166         mSubscriptionManager.addOnSubscriptionsChangedListener(
167                 new OnSubscriptionsChangedListener() {
168                     @Override
169                     public void onSubscriptionsChanged() {
170                         updatePhoneStateListeners();
171                     }
172                 });
173     }
174 
createSignalInfoToneGenerator()175     private void createSignalInfoToneGenerator() {
176         // Instantiate the ToneGenerator for SignalInfo and CallWaiting
177         // TODO: We probably don't need the mSignalInfoToneGenerator instance
178         // around forever. Need to change it so as to create a ToneGenerator instance only
179         // when a tone is being played and releases it after its done playing.
180         if (mSignalInfoToneGenerator == null) {
181             try {
182                 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
183                         TONE_RELATIVE_VOLUME_SIGNALINFO);
184                 Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay");
185             } catch (RuntimeException e) {
186                 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
187                         "mSignalInfoToneGenerator: " + e);
188                 mSignalInfoToneGenerator = null;
189             }
190         } else {
191             Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping");
192         }
193     }
194 
195     @Override
handleMessage(Message msg)196     public void handleMessage(Message msg) {
197         switch (msg.what) {
198             case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
199                 log("RINGING... (new)");
200                 onNewRingingConnection((AsyncResult) msg.obj);
201                 break;
202 
203             case CallStateMonitor.PHONE_STATE_CHANGED:
204                 onPhoneStateChanged((AsyncResult) msg.obj);
205                 break;
206 
207             case CallStateMonitor.PHONE_DISCONNECT:
208                 if (DBG) log("DISCONNECT");
209                 onDisconnect((AsyncResult) msg.obj);
210                 break;
211 
212             case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
213                 onUnknownConnectionAppeared((AsyncResult) msg.obj);
214                 break;
215 
216             case CallStateMonitor.PHONE_STATE_DISPLAYINFO:
217                 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
218                 onDisplayInfo((AsyncResult) msg.obj);
219                 break;
220 
221             case CallStateMonitor.PHONE_STATE_SIGNALINFO:
222                 if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
223                 onSignalInfo((AsyncResult) msg.obj);
224                 break;
225 
226             case CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE:
227                 if (DBG) log("Received Display Info notification done event ...");
228                 PhoneDisplayMessage.dismissMessage();
229                 break;
230 
231             case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE:
232                 if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
233                 mApplication.handleOtaspEvent(msg);
234                 break;
235 
236             case CallStateMonitor.PHONE_ENHANCED_VP_ON:
237                 if (DBG) log("PHONE_ENHANCED_VP_ON...");
238                 if (!mVoicePrivacyState) {
239                     int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
240                     new InCallTonePlayer(toneToPlay).start();
241                     mVoicePrivacyState = true;
242                 }
243                 break;
244 
245             case CallStateMonitor.PHONE_ENHANCED_VP_OFF:
246                 if (DBG) log("PHONE_ENHANCED_VP_OFF...");
247                 if (mVoicePrivacyState) {
248                     int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
249                     new InCallTonePlayer(toneToPlay).start();
250                     mVoicePrivacyState = false;
251                 }
252                 break;
253 
254             case CallStateMonitor.PHONE_SUPP_SERVICE_FAILED:
255                 if (DBG) log("PHONE_SUPP_SERVICE_FAILED...");
256                 onSuppServiceFailed((AsyncResult) msg.obj);
257                 break;
258 
259             case CallStateMonitor.PHONE_TTY_MODE_RECEIVED:
260                 if (DBG) log("Received PHONE_TTY_MODE_RECEIVED event");
261                 onTtyModeReceived((AsyncResult) msg.obj);
262                 break;
263 
264             default:
265                 // super.handleMessage(msg);
266         }
267     }
268 
269     /**
270      * Handles a "new ringing connection" event from the telephony layer.
271      */
onNewRingingConnection(AsyncResult r)272     private void onNewRingingConnection(AsyncResult r) {
273         Connection c = (Connection) r.result;
274         log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
275         Call ringing = c.getCall();
276         Phone phone = ringing.getPhone();
277 
278         // Check for a few cases where we totally ignore incoming calls.
279         if (ignoreAllIncomingCalls(phone)) {
280             // Immediately reject the call, without even indicating to the user
281             // that an incoming call occurred.  (This will generally send the
282             // caller straight to voicemail, just as if we *had* shown the
283             // incoming-call UI and the user had declined the call.)
284             PhoneUtils.hangupRingingCall(ringing);
285             return;
286         }
287 
288         if (!c.isRinging()) {
289             Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
290             // This is a very strange case: an incoming call that stopped
291             // ringing almost instantly after the onNewRingingConnection()
292             // event.  There's nothing we can do here, so just bail out
293             // without doing anything.  (But presumably we'll log it in
294             // the call log when the disconnect event comes in...)
295             return;
296         }
297 
298         // Stop any signalInfo tone being played on receiving a Call
299         stopSignalInfoTone();
300 
301         Call.State state = c.getState();
302         // State will be either INCOMING or WAITING.
303         if (VDBG) log("- connection is ringing!  state = " + state);
304         // if (DBG) PhoneUtils.dumpCallState(mPhone);
305 
306         // No need to do any service state checks here (like for
307         // "emergency mode"), since in those states the SIM won't let
308         // us get incoming connections in the first place.
309 
310         // TODO: Consider sending out a serialized broadcast Intent here
311         // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
312         // ringer and going to the in-call UI.  The intent should contain
313         // the caller-id info for the current connection, and say whether
314         // it would be a "call waiting" call or a regular ringing call.
315         // If anybody consumed the broadcast, we'd bail out without
316         // ringing or bringing up the in-call UI.
317         //
318         // This would give 3rd party apps a chance to listen for (and
319         // intercept) new ringing connections.  An app could reject the
320         // incoming call by consuming the broadcast and doing nothing, or
321         // it could "pick up" the call (without any action by the user!)
322         // via some future TelephonyManager API.
323         //
324         // See bug 1312336 for more details.
325         // We'd need to protect this with a new "intercept incoming calls"
326         // system permission.
327 
328         // Obtain a partial wake lock to make sure the CPU doesn't go to
329         // sleep before we finish bringing up the InCallScreen.
330         // (This will be upgraded soon to a full wake lock; see
331         // showIncomingCall().)
332         if (VDBG) log("Holding wake lock on new incoming connection.");
333         mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL);
334 
335         // Note we *don't* post a status bar notification here, since
336         // we're not necessarily ready to actually show the incoming call
337         // to the user.  (For calls in the INCOMING state, at least, we
338         // still need to run a caller-id query, and we may not even ring
339         // at all if the "send directly to voicemail" flag is set.)
340         //
341         // Instead, we update the notification (and potentially launch the
342         // InCallScreen) from the showIncomingCall() method, which runs
343         // when the caller-id query completes or times out.
344 
345         if (VDBG) log("- onNewRingingConnection() done.");
346     }
347 
348     /**
349      * Determines whether or not we're allowed to present incoming calls to the
350      * user, based on the capabilities and/or current state of the device.
351      *
352      * If this method returns true, that means we should immediately reject the
353      * current incoming call, without even indicating to the user that an
354      * incoming call occurred.
355      *
356      * (We only reject incoming calls in a few cases, like during an OTASP call
357      * when we can't interrupt the user, or if the device hasn't completed the
358      * SetupWizard yet.  We also don't allow incoming calls on non-voice-capable
359      * devices.  But note that we *always* allow incoming calls while in ECM.)
360      *
361      * @return true if we're *not* allowed to present an incoming call to
362      * the user.
363      */
ignoreAllIncomingCalls(Phone phone)364     private boolean ignoreAllIncomingCalls(Phone phone) {
365         // Incoming calls are totally ignored on non-voice-capable devices.
366         if (!PhoneGlobals.sVoiceCapable) {
367             // ...but still log a warning, since we shouldn't have gotten this
368             // event in the first place!  (Incoming calls *should* be blocked at
369             // the telephony layer on non-voice-capable capable devices.)
370             Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring...");
371             return true;
372         }
373 
374         // In ECM (emergency callback mode), we ALWAYS allow incoming calls
375         // to get through to the user.  (Note that ECM is applicable only to
376         // voice-capable CDMA devices).
377         if (PhoneUtils.isPhoneInEcm(phone)) {
378             if (DBG) log("Incoming call while in ECM: always allow...");
379             return false;
380         }
381 
382         // Incoming calls are totally ignored if the device isn't provisioned yet.
383         boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(),
384             Settings.Global.DEVICE_PROVISIONED, 0) != 0;
385         if (!provisioned) {
386             Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
387             return true;
388         }
389 
390         // Incoming calls are totally ignored if an OTASP call is active.
391         if (TelephonyCapabilities.supportsOtasp(phone)) {
392             boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState
393                     == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
394             boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState
395                     == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG);
396             boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState;
397 
398             if (spcState) {
399                 Log.i(LOG_TAG, "Ignoring incoming call: OTA call is active");
400                 return true;
401             } else if (activateState || dialogState) {
402                 // We *are* allowed to receive incoming calls at this point.
403                 // But clear out any residual OTASP UI first.
404                 // TODO: It's an MVC violation to twiddle the OTA UI state here;
405                 // we should instead provide a higher-level API via OtaUtils.
406                 if (dialogState) mApplication.dismissOtaDialogs();
407                 mApplication.clearOtaState();
408                 return false;
409             }
410         }
411 
412         // Normal case: allow this call to be presented to the user.
413         return false;
414     }
415 
onUnknownConnectionAppeared(AsyncResult r)416     private void onUnknownConnectionAppeared(AsyncResult r) {
417         PhoneConstants.State state = mCM.getState();
418 
419         if (state == PhoneConstants.State.OFFHOOK) {
420             if (DBG) log("unknown connection appeared...");
421 
422             onPhoneStateChanged(r);
423         }
424     }
425 
426     /**
427      * Updates the phone UI in response to phone state changes.
428      *
429      * Watch out: certain state changes are actually handled by their own
430      * specific methods:
431      *   - see onNewRingingConnection() for new incoming calls
432      *   - see onDisconnect() for calls being hung up or disconnected
433      */
onPhoneStateChanged(AsyncResult r)434     private void onPhoneStateChanged(AsyncResult r) {
435         PhoneConstants.State state = mCM.getState();
436         if (VDBG) log("onPhoneStateChanged: state = " + state);
437 
438         // Turn status bar notifications on or off depending upon the state
439         // of the phone.  Notification Alerts (audible or vibrating) should
440         // be on if and only if the phone is IDLE.
441         mApplication.notificationMgr.statusBarHelper
442                 .enableNotificationAlerts(state == PhoneConstants.State.IDLE);
443 
444         Phone fgPhone = mCM.getFgPhone();
445         if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
446             if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE)
447                     && ((mPreviousCdmaCallState == Call.State.DIALING)
448                     ||  (mPreviousCdmaCallState == Call.State.ALERTING))) {
449                 if (mIsCdmaRedialCall) {
450                     int toneToPlay = InCallTonePlayer.TONE_REDIAL;
451                     new InCallTonePlayer(toneToPlay).start();
452                 }
453                 // Stop any signal info tone when call moves to ACTIVE state
454                 stopSignalInfoTone();
455             }
456             mPreviousCdmaCallState = fgPhone.getForegroundCall().getState();
457         }
458 
459         // Have the PhoneApp recompute its mShowBluetoothIndication
460         // flag based on the (new) telephony state.
461         // There's no need to force a UI update since we update the
462         // in-call notification ourselves (below), and the InCallScreen
463         // listens for phone state changes itself.
464         mBluetoothManager.updateBluetoothIndication();
465 
466         // Update the phone state and other sensor/lock.
467         mApplication.updatePhoneState(state);
468 
469         if (state == PhoneConstants.State.OFFHOOK) {
470 
471             if (VDBG) log("onPhoneStateChanged: OFF HOOK");
472             // make sure audio is in in-call mode now
473             PhoneUtils.setAudioMode(mCM);
474         }
475     }
476 
updateCallNotifierRegistrationsAfterRadioTechnologyChange()477     void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
478         if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
479 
480         // Instantiate mSignalInfoToneGenerator
481         createSignalInfoToneGenerator();
482     }
483 
onDisconnect(AsyncResult r)484     private void onDisconnect(AsyncResult r) {
485         if (VDBG) log("onDisconnect()...  CallManager state: " + mCM.getState());
486 
487         mVoicePrivacyState = false;
488         Connection c = (Connection) r.result;
489         if (c != null) {
490             log("onDisconnect: cause = " + DisconnectCause.toString(c.getDisconnectCause())
491                   + ", incoming = " + c.isIncoming()
492                   + ", date = " + c.getCreateTime());
493         } else {
494             Log.w(LOG_TAG, "onDisconnect: null connection");
495         }
496 
497         int autoretrySetting = 0;
498         if ((c != null) &&
499                 (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
500             autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
501                     getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
502         }
503 
504         // Stop any signalInfo tone being played when a call gets ended
505         stopSignalInfoTone();
506 
507         if ((c != null) &&
508                 (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
509             // Resetting the CdmaPhoneCallState members
510             mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
511         }
512 
513         // If this is the end of an OTASP call, pass it on to the PhoneApp.
514         if (c != null && TelephonyCapabilities.supportsOtasp(c.getCall().getPhone())) {
515             final String number = c.getAddress();
516             if (c.getCall().getPhone().isOtaSpNumber(number)) {
517                 if (DBG) log("onDisconnect: this was an OTASP call!");
518                 mApplication.handleOtaspDisconnect();
519             }
520         }
521 
522         // Check for the various tones we might need to play (thru the
523         // earpiece) after a call disconnects.
524         int toneToPlay = InCallTonePlayer.TONE_NONE;
525 
526         // If we don't need to play BUSY or CONGESTION, then play the
527         // "call ended" tone if this was a "regular disconnect" (i.e. a
528         // normal call where one end or the other hung up) *and* this
529         // disconnect event caused the phone to become idle.  (In other
530         // words, we *don't* play the sound if one call hangs up but
531         // there's still an active call on the other line.)
532         // TODO: We may eventually want to disable this via a preference.
533         if ((toneToPlay == InCallTonePlayer.TONE_NONE)
534             && (mCM.getState() == PhoneConstants.State.IDLE)
535             && (c != null)) {
536             int cause = c.getDisconnectCause();
537             if ((cause == DisconnectCause.NORMAL)  // remote hangup
538                 || (cause == DisconnectCause.LOCAL)) {  // local hangup
539                 if (VDBG) log("- need to play CALL_ENDED tone!");
540                 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
541                 mIsCdmaRedialCall = false;
542             }
543         }
544 
545         // All phone calls are disconnected.
546         if (mCM.getState() == PhoneConstants.State.IDLE) {
547             // Don't reset the audio mode or bluetooth/speakerphone state
548             // if we still need to let the user hear a tone through the earpiece.
549             if (toneToPlay == InCallTonePlayer.TONE_NONE) {
550                 resetAudioStateAfterDisconnect();
551             }
552         }
553 
554         if (c != null) {
555             mCallLogger.logCall(c);
556 
557             final String number = c.getAddress();
558             final Phone phone = c.getCall().getPhone();
559             final boolean isEmergencyNumber =
560                     PhoneNumberUtils.isLocalEmergencyNumber(mApplication, number);
561 
562             // Possibly play a "post-disconnect tone" thru the earpiece.
563             // We do this here, rather than from the InCallScreen
564             // activity, since we need to do this even if you're not in
565             // the Phone UI at the moment the connection ends.
566             if (toneToPlay != InCallTonePlayer.TONE_NONE) {
567                 if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
568                 new InCallTonePlayer(toneToPlay).start();
569 
570                 // TODO: alternatively, we could start an InCallTonePlayer
571                 // here with an "unlimited" tone length,
572                 // and manually stop it later when this connection truly goes
573                 // away.  (The real connection over the network was closed as soon
574                 // as we got the BUSY message.  But our telephony layer keeps the
575                 // connection open for a few extra seconds so we can show the
576                 // "busy" indication to the user.  We could stop the busy tone
577                 // when *that* connection's "disconnect" event comes in.)
578             }
579 
580             final int cause = c.getDisconnectCause();
581             if (((mPreviousCdmaCallState == Call.State.DIALING)
582                     || (mPreviousCdmaCallState == Call.State.ALERTING))
583                     && (!isEmergencyNumber)
584                     && (cause != DisconnectCause.INCOMING_MISSED )
585                     && (cause != DisconnectCause.NORMAL)
586                     && (cause != DisconnectCause.LOCAL)
587                     && (cause != DisconnectCause.INCOMING_REJECTED)) {
588                 if (!mIsCdmaRedialCall) {
589                     if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
590                         // TODO: (Moto): The contact reference data may need to be stored and use
591                         // here when redialing a call. For now, pass in NULL as the URI parameter.
592                         final int status =
593                                 PhoneUtils.placeCall(mApplication, phone, number, null, false);
594                         if (status != PhoneUtils.CALL_STATUS_FAILED) {
595                             mIsCdmaRedialCall = true;
596                         }
597                     } else {
598                         mIsCdmaRedialCall = false;
599                     }
600                 } else {
601                     mIsCdmaRedialCall = false;
602                 }
603             }
604         }
605     }
606 
607     /**
608      * Resets the audio mode and speaker state when a call ends.
609      */
resetAudioStateAfterDisconnect()610     private void resetAudioStateAfterDisconnect() {
611         if (VDBG) log("resetAudioStateAfterDisconnect()...");
612 
613         if (mBluetoothHeadset != null) {
614             mBluetoothHeadset.disconnectAudio();
615         }
616 
617         // call turnOnSpeaker() with state=false and store=true even if speaker
618         // is already off to reset user requested speaker state.
619         PhoneUtils.turnOnSpeaker(mApplication, false, true);
620 
621         PhoneUtils.setAudioMode(mCM);
622     }
623 
624     /**
625      * Helper class to play tones through the earpiece (or speaker / BT)
626      * during a call, using the ToneGenerator.
627      *
628      * To use, just instantiate a new InCallTonePlayer
629      * (passing in the TONE_* constant for the tone you want)
630      * and start() it.
631      *
632      * When we're done playing the tone, if the phone is idle at that
633      * point, we'll reset the audio routing and speaker state.
634      * (That means that for tones that get played *after* a call
635      * disconnects, like "busy" or "congestion" or "call ended", you
636      * should NOT call resetAudioStateAfterDisconnect() yourself.
637      * Instead, just start the InCallTonePlayer, which will automatically
638      * defer the resetAudioStateAfterDisconnect() call until the tone
639      * finishes playing.)
640      */
641     private class InCallTonePlayer extends Thread {
642         private int mToneId;
643         private int mState;
644         // The possible tones we can play.
645         public static final int TONE_NONE = 0;
646         public static final int TONE_CALL_WAITING = 1;
647         public static final int TONE_BUSY = 2;
648         public static final int TONE_CONGESTION = 3;
649         public static final int TONE_CALL_ENDED = 4;
650         public static final int TONE_VOICE_PRIVACY = 5;
651         public static final int TONE_REORDER = 6;
652         public static final int TONE_INTERCEPT = 7;
653         public static final int TONE_CDMA_DROP = 8;
654         public static final int TONE_OUT_OF_SERVICE = 9;
655         public static final int TONE_REDIAL = 10;
656         public static final int TONE_OTA_CALL_END = 11;
657         public static final int TONE_UNOBTAINABLE_NUMBER = 13;
658 
659         // The tone volume relative to other sounds in the stream
660         static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
661         static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
662         static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
663 
664         // Buffer time (in msec) to add on to tone timeout value.
665         // Needed mainly when the timeout value for a tone is the
666         // exact duration of the tone itself.
667         static final int TONE_TIMEOUT_BUFFER = 20;
668 
669         // The tone state
670         static final int TONE_OFF = 0;
671         static final int TONE_ON = 1;
672         static final int TONE_STOPPED = 2;
673 
InCallTonePlayer(int toneId)674         InCallTonePlayer(int toneId) {
675             super();
676             mToneId = toneId;
677             mState = TONE_OFF;
678         }
679 
680         @Override
run()681         public void run() {
682             log("InCallTonePlayer.run(toneId = " + mToneId + ")...");
683 
684             int toneType = 0;  // passed to ToneGenerator.startTone()
685             int toneVolume;  // passed to the ToneGenerator constructor
686             int toneLengthMillis;
687             int phoneType = mCM.getFgPhone().getPhoneType();
688 
689             switch (mToneId) {
690                 case TONE_CALL_WAITING:
691                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
692                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
693                     // Call waiting tone is stopped by stopTone() method
694                     toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
695                     break;
696                 case TONE_BUSY:
697                     if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
698                         toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
699                         toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
700                         toneLengthMillis = 1000;
701                     } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM
702                             || phoneType == PhoneConstants.PHONE_TYPE_SIP
703                             || phoneType == PhoneConstants.PHONE_TYPE_IMS
704                             || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) {
705                         toneType = ToneGenerator.TONE_SUP_BUSY;
706                         toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
707                         toneLengthMillis = 4000;
708                     } else {
709                         throw new IllegalStateException("Unexpected phone type: " + phoneType);
710                     }
711                     break;
712                 case TONE_CONGESTION:
713                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
714                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
715                     toneLengthMillis = 4000;
716                     break;
717 
718                 case TONE_CALL_ENDED:
719                     toneType = ToneGenerator.TONE_PROP_PROMPT;
720                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
721                     toneLengthMillis = 200;
722                     break;
723                  case TONE_OTA_CALL_END:
724                     if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone ==
725                             OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) {
726                         toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD;
727                         toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
728                         toneLengthMillis = 750;
729                     } else {
730                         toneType = ToneGenerator.TONE_PROP_PROMPT;
731                         toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
732                         toneLengthMillis = 200;
733                     }
734                     break;
735                 case TONE_VOICE_PRIVACY:
736                     toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
737                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
738                     toneLengthMillis = 5000;
739                     break;
740                 case TONE_REORDER:
741                     toneType = ToneGenerator.TONE_CDMA_REORDER;
742                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
743                     toneLengthMillis = 4000;
744                     break;
745                 case TONE_INTERCEPT:
746                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
747                     toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
748                     toneLengthMillis = 500;
749                     break;
750                 case TONE_CDMA_DROP:
751                 case TONE_OUT_OF_SERVICE:
752                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
753                     toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
754                     toneLengthMillis = 375;
755                     break;
756                 case TONE_REDIAL:
757                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
758                     toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
759                     toneLengthMillis = 5000;
760                     break;
761                 case TONE_UNOBTAINABLE_NUMBER:
762                     toneType = ToneGenerator.TONE_SUP_ERROR;
763                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
764                     toneLengthMillis = 4000;
765                     break;
766                 default:
767                     throw new IllegalArgumentException("Bad toneId: " + mToneId);
768             }
769 
770             // If the mToneGenerator creation fails, just continue without it.  It is
771             // a local audio signal, and is not as important.
772             ToneGenerator toneGenerator;
773             try {
774                 int stream;
775                 if (mBluetoothHeadset != null) {
776                     stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
777                         AudioManager.STREAM_VOICE_CALL;
778                 } else {
779                     stream = AudioManager.STREAM_VOICE_CALL;
780                 }
781                 toneGenerator = new ToneGenerator(stream, toneVolume);
782                 // if (DBG) log("- created toneGenerator: " + toneGenerator);
783             } catch (RuntimeException e) {
784                 Log.w(LOG_TAG,
785                       "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
786                 toneGenerator = null;
787             }
788 
789             // Using the ToneGenerator (with the CALL_WAITING / BUSY /
790             // CONGESTION tones at least), the ToneGenerator itself knows
791             // the right pattern of tones to play; we do NOT need to
792             // manually start/stop each individual tone, or manually
793             // insert the correct delay between tones.  (We just start it
794             // and let it run for however long we want the tone pattern to
795             // continue.)
796             //
797             // TODO: When we stop the ToneGenerator in the middle of a
798             // "tone pattern", it sounds bad if we cut if off while the
799             // tone is actually playing.  Consider adding API to the
800             // ToneGenerator to say "stop at the next silent part of the
801             // pattern", or simply "play the pattern N times and then
802             // stop."
803             boolean needToStopTone = true;
804             boolean okToPlayTone = false;
805 
806             if (toneGenerator != null) {
807                 int ringerMode = mAudioManager.getRingerMode();
808                 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
809                     if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) {
810                         if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
811                                 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
812                             if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType);
813                             okToPlayTone = true;
814                             needToStopTone = false;
815                         }
816                     } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) ||
817                             (toneType == ToneGenerator.TONE_CDMA_REORDER) ||
818                             (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) ||
819                             (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) ||
820                             (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) {
821                         if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
822                             if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType);
823                             okToPlayTone = true;
824                             needToStopTone = false;
825                         }
826                     } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) ||
827                                (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) {
828                         if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
829                                 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
830                             if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType);
831                             okToPlayTone = true;
832                             needToStopTone = false;
833                         }
834                     } else { // For the rest of the tones, always OK to play.
835                         okToPlayTone = true;
836                     }
837                 } else {  // Not "CDMA"
838                     okToPlayTone = true;
839                 }
840 
841                 synchronized (this) {
842                     if (okToPlayTone && mState != TONE_STOPPED) {
843                         mState = TONE_ON;
844                         toneGenerator.startTone(toneType);
845                         try {
846                             wait(toneLengthMillis + TONE_TIMEOUT_BUFFER);
847                         } catch  (InterruptedException e) {
848                             Log.w(LOG_TAG,
849                                   "InCallTonePlayer stopped: " + e);
850                         }
851                         if (needToStopTone) {
852                             toneGenerator.stopTone();
853                         }
854                     }
855                     // if (DBG) log("- InCallTonePlayer: done playing.");
856                     toneGenerator.release();
857                     mState = TONE_OFF;
858                 }
859             }
860 
861             // Finally, do the same cleanup we otherwise would have done
862             // in onDisconnect().
863             //
864             // (But watch out: do NOT do this if the phone is in use,
865             // since some of our tones get played *during* a call (like
866             // CALL_WAITING) and we definitely *don't*
867             // want to reset the audio mode / speaker / bluetooth after
868             // playing those!
869             // This call is really here for use with tones that get played
870             // *after* a call disconnects, like "busy" or "congestion" or
871             // "call ended", where the phone has already become idle but
872             // we need to defer the resetAudioStateAfterDisconnect() call
873             // till the tone finishes playing.)
874             if (mCM.getState() == PhoneConstants.State.IDLE) {
875                 resetAudioStateAfterDisconnect();
876             }
877         }
878 
stopTone()879         public void stopTone() {
880             synchronized (this) {
881                 if (mState == TONE_ON) {
882                     notify();
883                 }
884                 mState = TONE_STOPPED;
885             }
886         }
887     }
888 
889     /**
890      * Displays a notification when the phone receives a DisplayInfo record.
891      */
onDisplayInfo(AsyncResult r)892     private void onDisplayInfo(AsyncResult r) {
893         // Extract the DisplayInfo String from the message
894         CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
895 
896         if (displayInfoRec != null) {
897             String displayInfo = displayInfoRec.alpha;
898             if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
899             PhoneDisplayMessage.displayNetworkMessage(mApplication, displayInfo);
900 
901             // start a timer that kills the dialog
902             sendEmptyMessageDelayed(CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE,
903                     SHOW_MESSAGE_NOTIFICATION_TIME);
904         }
905     }
906 
907     /**
908      * Displays a notification when the phone receives a notice that a supplemental
909      * service has failed.
910      * TODO: This is a NOOP if it isn't for conferences or resuming call failures right now.
911      */
onSuppServiceFailed(AsyncResult r)912     private void onSuppServiceFailed(AsyncResult r) {
913         if (r.result != Phone.SuppService.CONFERENCE && r.result != Phone.SuppService.RESUME) {
914             if (DBG) log("onSuppServiceFailed: not a merge or resume failure event");
915             return;
916         }
917 
918         String mergeFailedString = "";
919         if (r.result == Phone.SuppService.CONFERENCE) {
920             if (DBG) log("onSuppServiceFailed: displaying merge failure message");
921             mergeFailedString = mApplication.getResources().getString(
922                     R.string.incall_error_supp_service_conference);
923         } else if (r.result == Phone.SuppService.RESUME) {
924             if (DBG) log("onSuppServiceFailed: displaying merge failure message");
925             mergeFailedString = mApplication.getResources().getString(
926                     R.string.incall_error_supp_service_switch);
927         }
928         PhoneDisplayMessage.displayErrorMessage(mApplication, mergeFailedString);
929 
930         // start a timer that kills the dialog
931         sendEmptyMessageDelayed(CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE,
932                 SHOW_MESSAGE_NOTIFICATION_TIME);
933     }
934 
updatePhoneStateListeners()935     public void updatePhoneStateListeners() {
936         List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
937 
938         // Unregister phone listeners for inactive subscriptions.
939         Iterator<Integer> itr = mPhoneStateListeners.keySet().iterator();
940         while (itr.hasNext()) {
941             int subId = itr.next();
942             if (subInfos == null || !containsSubId(subInfos, subId)) {
943                 // Hide the outstanding notifications.
944                 mApplication.notificationMgr.updateMwi(subId, false);
945                 mApplication.notificationMgr.updateCfi(subId, false);
946 
947                 // Listening to LISTEN_NONE removes the listener.
948                 mTelephonyManager.listen(
949                         mPhoneStateListeners.get(subId), PhoneStateListener.LISTEN_NONE);
950                 itr.remove();
951             }
952         }
953 
954         if (subInfos == null) {
955             return;
956         }
957 
958         // Register new phone listeners for active subscriptions.
959         for (int i = 0; i < subInfos.size(); i++) {
960             int subId = subInfos.get(i).getSubscriptionId();
961             if (!mPhoneStateListeners.containsKey(subId)) {
962                 CallNotifierPhoneStateListener listener = new CallNotifierPhoneStateListener(subId);
963                 mTelephonyManager.listen(listener,
964                         PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
965                         | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
966                 mPhoneStateListeners.put(subId, listener);
967             }
968         }
969     }
970 
971     /**
972      * @return {@code true} if the list contains SubscriptionInfo with the given subscription id.
973      */
containsSubId(List<SubscriptionInfo> subInfos, int subId)974     private boolean containsSubId(List<SubscriptionInfo> subInfos, int subId) {
975         if (subInfos == null) {
976             return false;
977         }
978 
979         for (int i = 0; i < subInfos.size(); i++) {
980             if (subInfos.get(i).getSubscriptionId() == subId) {
981                 return true;
982             }
983         }
984         return false;
985     }
986 
987     /**
988      * Displays a notification when the phone receives a notice that TTY mode
989      * has changed on remote end.
990      */
onTtyModeReceived(AsyncResult r)991     private void onTtyModeReceived(AsyncResult r) {
992         if (DBG) log("TtyModeReceived: displaying notification message");
993 
994         int resId = 0;
995         switch (((Integer)r.result).intValue()) {
996             case TelecomManager.TTY_MODE_FULL:
997                 resId = com.android.internal.R.string.peerTtyModeFull;
998                 break;
999             case TelecomManager.TTY_MODE_HCO:
1000                 resId = com.android.internal.R.string.peerTtyModeHco;
1001                 break;
1002             case TelecomManager.TTY_MODE_VCO:
1003                 resId = com.android.internal.R.string.peerTtyModeVco;
1004                 break;
1005             case TelecomManager.TTY_MODE_OFF:
1006                 resId = com.android.internal.R.string.peerTtyModeOff;
1007                 break;
1008             default:
1009                 Log.e(LOG_TAG, "Unsupported TTY mode: " + r.result);
1010                 break;
1011         }
1012         if (resId != 0) {
1013             PhoneDisplayMessage.displayNetworkMessage(mApplication,
1014                     mApplication.getResources().getString(resId));
1015 
1016             // start a timer that kills the dialog
1017             sendEmptyMessageDelayed(
1018                     CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE,
1019                     SHOW_MESSAGE_NOTIFICATION_TIME);
1020         }
1021     }
1022 
1023     /**
1024      * Helper class to play SignalInfo tones using the ToneGenerator.
1025      *
1026      * To use, just instantiate a new SignalInfoTonePlayer
1027      * (passing in the ToneID constant for the tone you want)
1028      * and start() it.
1029      */
1030     private class SignalInfoTonePlayer extends Thread {
1031         private int mToneId;
1032 
SignalInfoTonePlayer(int toneId)1033         SignalInfoTonePlayer(int toneId) {
1034             super();
1035             mToneId = toneId;
1036         }
1037 
1038         @Override
run()1039         public void run() {
1040             log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
1041             createSignalInfoToneGenerator();
1042             if (mSignalInfoToneGenerator != null) {
1043                 //First stop any ongoing SignalInfo tone
1044                 mSignalInfoToneGenerator.stopTone();
1045 
1046                 //Start playing the new tone if its a valid tone
1047                 mSignalInfoToneGenerator.startTone(mToneId);
1048             }
1049         }
1050     }
1051 
1052     /**
1053      * Plays a tone when the phone receives a SignalInfo record.
1054      */
onSignalInfo(AsyncResult r)1055     private void onSignalInfo(AsyncResult r) {
1056         // Signal Info are totally ignored on non-voice-capable devices.
1057         if (!PhoneGlobals.sVoiceCapable) {
1058             Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring...");
1059             return;
1060         }
1061 
1062         if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) {
1063             // Do not start any new SignalInfo tone when Call state is INCOMING
1064             // and stop any previous SignalInfo tone which is being played
1065             stopSignalInfoTone();
1066         } else {
1067             // Extract the SignalInfo String from the message
1068             CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
1069             // Only proceed if a Signal info is present.
1070             if (signalInfoRec != null) {
1071                 boolean isPresent = signalInfoRec.isPresent;
1072                 if (DBG) log("onSignalInfo: isPresent=" + isPresent);
1073                 if (isPresent) {// if tone is valid
1074                     int uSignalType = signalInfoRec.signalType;
1075                     int uAlertPitch = signalInfoRec.alertPitch;
1076                     int uSignal = signalInfoRec.signal;
1077 
1078                     if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
1079                             uAlertPitch + ", uSignal=" + uSignal);
1080                     //Map the Signal to a ToneGenerator ToneID only if Signal info is present
1081                     int toneID = SignalToneUtil.getAudioToneFromSignalInfo
1082                             (uSignalType, uAlertPitch, uSignal);
1083 
1084                     //Create the SignalInfo tone player and pass the ToneID
1085                     new SignalInfoTonePlayer(toneID).start();
1086                 }
1087             }
1088         }
1089     }
1090 
1091     /**
1092      * Stops a SignalInfo tone in the following condition
1093      * 1 - On receiving a New Ringing Call
1094      * 2 - On disconnecting a call
1095      * 3 - On answering a Call Waiting Call
1096      */
stopSignalInfoTone()1097     /* package */ void stopSignalInfoTone() {
1098         if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player");
1099         new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
1100     }
1101 
1102     /**
1103      * Return the private variable mPreviousCdmaCallState.
1104      */
getPreviousCdmaCallState()1105     /* package */ Call.State getPreviousCdmaCallState() {
1106         return mPreviousCdmaCallState;
1107     }
1108 
1109     /**
1110      * Return the private variable mVoicePrivacyState.
1111      */
getVoicePrivacyState()1112     /* package */ boolean getVoicePrivacyState() {
1113         return mVoicePrivacyState;
1114     }
1115 
1116     /**
1117      * Return the private variable mIsCdmaRedialCall.
1118      */
getIsCdmaRedialCall()1119     /* package */ boolean getIsCdmaRedialCall() {
1120         return mIsCdmaRedialCall;
1121     }
1122 
1123     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
1124            new BluetoothProfile.ServiceListener() {
1125                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
1126                     mBluetoothHeadset = (BluetoothHeadset) proxy;
1127                     if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
1128                 }
1129 
1130                 public void onServiceDisconnected(int profile) {
1131                     mBluetoothHeadset = null;
1132                 }
1133             };
1134 
1135     private class CallNotifierPhoneStateListener extends PhoneStateListener {
CallNotifierPhoneStateListener(int subId)1136         public CallNotifierPhoneStateListener(int subId) {
1137             super(subId);
1138         }
1139 
1140         @Override
onMessageWaitingIndicatorChanged(boolean visible)1141         public void onMessageWaitingIndicatorChanged(boolean visible) {
1142             if (VDBG) log("onMessageWaitingIndicatorChanged(): " + this.mSubId + " " + visible);
1143             mApplication.notificationMgr.updateMwi(this.mSubId, visible);
1144         }
1145 
1146         @Override
onCallForwardingIndicatorChanged(boolean visible)1147         public void onCallForwardingIndicatorChanged(boolean visible) {
1148             if (VDBG) log("onCallForwardingIndicatorChanged(): " + this.mSubId + " " + visible);
1149             mApplication.notificationMgr.updateCfi(this.mSubId, visible);
1150         }
1151     };
1152 
log(String msg)1153     private void log(String msg) {
1154         Log.d(LOG_TAG, msg);
1155     }
1156 }
1157