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