• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.ActivityManagerNative;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.KeyguardManager;
23 import android.app.ProgressDialog;
24 import android.content.ComponentName;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.ServiceConnection;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.PackageManager;
32 import android.graphics.drawable.Drawable;
33 import android.media.AudioManager;
34 import android.net.Uri;
35 import android.os.AsyncResult;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.telephony.PhoneNumberUtils;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyEvent;
44 import android.view.LayoutInflater;
45 import android.view.View;
46 import android.view.WindowManager;
47 import android.widget.EditText;
48 import android.widget.Toast;
49 
50 import com.android.internal.telephony.Call;
51 import com.android.internal.telephony.CallStateException;
52 import com.android.internal.telephony.CallerInfo;
53 import com.android.internal.telephony.CallerInfoAsyncQuery;
54 import com.android.internal.telephony.Connection;
55 import com.android.internal.telephony.IExtendedNetworkService;
56 import com.android.internal.telephony.MmiCode;
57 import com.android.internal.telephony.Phone;
58 import com.android.internal.telephony.cdma.CdmaConnection;
59 
60 import java.util.Hashtable;
61 import java.util.Iterator;
62 import java.util.List;
63 
64 /**
65  * Misc utilities for the Phone app.
66  */
67 public class PhoneUtils {
68     private static final String LOG_TAG = "PhoneUtils";
69     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
70 
71     /** Control stack trace for Audio Mode settings */
72     private static final boolean DBG_SETAUDIOMODE_STACK = false;
73 
74     /** Identifier for the "Add Call" intent extra. */
75     static final String ADD_CALL_MODE_KEY = "add_call_mode";
76 
77     // Return codes from placeCall()
78     static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
79     static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
80     static final int CALL_STATUS_FAILED = 2;  // The call failed
81 
82     // State of the Phone's audio modes
83     // Each state can move to the other states, but within the state only certain
84     //  transitions for AudioManager.setMode() are allowed.
85     static final int AUDIO_IDLE = 0;  /** audio behaviour at phone idle */
86     static final int AUDIO_RINGING = 1;  /** audio behaviour while ringing */
87     static final int AUDIO_OFFHOOK = 2;  /** audio behaviour while in call. */
88     private static int sAudioBehaviourState = AUDIO_IDLE;
89 
90     /** Speaker state, persisting between wired headset connection events */
91     private static boolean sIsSpeakerEnabled = false;
92 
93     /** Hash table to store mute (Boolean) values based upon the connection.*/
94     private static Hashtable<Connection, Boolean> sConnectionMuteTable =
95         new Hashtable<Connection, Boolean>();
96 
97     /** Static handler for the connection/mute tracking */
98     private static ConnectionHandler mConnectionHandler;
99 
100     /** Phone state changed event*/
101     private static final int PHONE_STATE_CHANGED = -1;
102 
103     /** Define for not a special CNAP string */
104     private static final int CNAP_SPECIAL_CASE_NO = -1;
105 
106     // Extended network service interface instance
107     private static IExtendedNetworkService mNwService = null;
108     // used to cancel MMI command after 15 seconds timeout for NWService requirement
109     private static Message mMmiTimeoutCbMsg = null;
110 
111     /** Noise suppression status as selected by user */
112     private static boolean sIsNoiseSuppressionEnabled = true;
113 
114     /**
115      * Handler that tracks the connections and updates the value of the
116      * Mute settings for each connection as needed.
117      */
118     private static class ConnectionHandler extends Handler {
119         @Override
handleMessage(Message msg)120         public void handleMessage(Message msg) {
121             AsyncResult ar = (AsyncResult) msg.obj;
122             switch (msg.what) {
123                 case PHONE_STATE_CHANGED:
124                     if (DBG) log("ConnectionHandler: updating mute state for each connection");
125 
126                     Phone phone = (Phone) ar.userObj;
127 
128                     // update the foreground connections, if there are new connections.
129                     List<Connection> fgConnections = phone.getForegroundCall().getConnections();
130                     for (Connection cn : fgConnections) {
131                         if (sConnectionMuteTable.get(cn) == null) {
132                             sConnectionMuteTable.put(cn, Boolean.FALSE);
133                         }
134                     }
135 
136                     // update the background connections, if there are new connections.
137                     List<Connection> bgConnections = phone.getBackgroundCall().getConnections();
138                     for (Connection cn : bgConnections) {
139                         if (sConnectionMuteTable.get(cn) == null) {
140                             sConnectionMuteTable.put(cn, Boolean.FALSE);
141                         }
142                     }
143 
144                     // Check to see if there are any lingering connections here
145                     // (disconnected connections), use old-school iterators to avoid
146                     // concurrent modification exceptions.
147                     Connection cn;
148                     for (Iterator<Connection> cnlist = sConnectionMuteTable.keySet().iterator();
149                             cnlist.hasNext();) {
150                         cn = cnlist.next();
151                         if (!fgConnections.contains(cn) && !bgConnections.contains(cn)) {
152                             if (DBG) log("connection: " + cn + "not accounted for, removing.");
153                             cnlist.remove();
154                         }
155                     }
156 
157                     // Restore the mute state of the foreground call if we're not IDLE,
158                     // otherwise just clear the mute state. This is really saying that
159                     // as long as there is one or more connections, we should update
160                     // the mute state with the earliest connection on the foreground
161                     // call, and that with no connections, we should be back to a
162                     // non-mute state.
163                     if (phone.getState() != Phone.State.IDLE) {
164                         restoreMuteState(phone);
165                     } else {
166                         setMuteInternal(phone, false);
167                     }
168 
169                     break;
170             }
171         }
172     }
173 
174 
175     private static ServiceConnection ExtendedNetworkServiceConnection = new ServiceConnection() {
176         public void onServiceConnected(ComponentName name, IBinder iBinder) {
177             if (DBG) log("Extended NW onServiceConnected");
178             mNwService = IExtendedNetworkService.Stub.asInterface(iBinder);
179         }
180 
181         public void onServiceDisconnected(ComponentName arg0) {
182             if (DBG) log("Extended NW onServiceDisconnected");
183             mNwService = null;
184         }
185     };
186 
187     /**
188      * Register the ConnectionHandler with the phone, to receive connection events
189      */
initializeConnectionHandler(Phone phone)190     public static void initializeConnectionHandler(Phone phone) {
191         if (mConnectionHandler == null) {
192             mConnectionHandler = new ConnectionHandler();
193         }
194 
195         phone.registerForPreciseCallStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, phone);
196         // Extended NW service
197         Intent intent = new Intent("com.android.ussd.IExtendedNetworkService");
198         phone.getContext().bindService(intent,
199                 ExtendedNetworkServiceConnection, Context.BIND_AUTO_CREATE);
200         if (DBG) log("Extended NW bindService IExtendedNetworkService");
201 
202     }
203 
204     /** This class is never instantiated. */
PhoneUtils()205     private PhoneUtils() {
206     }
207 
208     //static method to set the audio control state.
setAudioControlState(int newState)209     static void setAudioControlState(int newState) {
210         sAudioBehaviourState = newState;
211     }
212 
213     /**
214      * Answer the currently-ringing call.
215      *
216      * @return true if we answered the call, or false if there wasn't
217      *         actually a ringing incoming call, or some other error occurred.
218      *
219      * @see answerAndEndHolding()
220      * @see answerAndEndActive()
221      */
answerCall(Phone phone)222     static boolean answerCall(Phone phone) {
223         if (DBG) log("answerCall()...");
224 
225         // If the ringer is currently ringing and/or vibrating, stop it
226         // right now (before actually answering the call.)
227         PhoneApp.getInstance().getRinger().stopRing();
228 
229         PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
230 
231         boolean answered = false;
232         Call call = phone.getRingingCall();
233         PhoneApp app = PhoneApp.getInstance();
234         boolean phoneIsCdma = (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA);
235         BluetoothHandsfree bthf = null;
236 
237         if (phoneIsCdma) {
238             // Stop any signalInfo tone being played when a Call waiting gets answered
239             if (call.getState() == Call.State.WAITING) {
240                 final CallNotifier notifier = app.notifier;
241                 notifier.stopSignalInfoTone();
242             }
243         }
244 
245         if (call != null && call.isRinging()) {
246             if (DBG) log("answerCall: call state = " + call.getState());
247             try {
248                 if (phoneIsCdma) {
249                     if (app.cdmaPhoneCallState.getCurrentCallState()
250                             == CdmaPhoneCallState.PhoneCallState.IDLE) {
251                         // This is the FIRST incoming call being answered.
252                         // Set the Phone Call State to SINGLE_ACTIVE
253                         app.cdmaPhoneCallState.setCurrentCallState(
254                                 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
255                     } else {
256                         // This is the CALL WAITING call being answered.
257                         // Set the Phone Call State to CONF_CALL
258                         app.cdmaPhoneCallState.setCurrentCallState(
259                                 CdmaPhoneCallState.PhoneCallState.CONF_CALL);
260                         // Enable "Add Call" option after answering a Call Waiting as the user
261                         // should be allowed to add another call in case one of the parties
262                         // drops off
263                         app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
264 
265                         // If a BluetoothHandsfree is valid we need to set the second call state
266                         // so that the Bluetooth client can update the Call state correctly when
267                         // a call waiting is answered from the Phone.
268                         bthf = app.getBluetoothHandsfree();
269                         if (bthf != null) {
270                             bthf.cdmaSetSecondCallState(true);
271                         }
272                     }
273                 }
274 
275                 //if (DBG) log("sPhone.acceptCall");
276                 phone.acceptCall();
277                 answered = true;
278                 if (phoneIsCdma) {
279                     // automatically reset mute state to unmuted for CDMA
280                     // TODO: Would GSM want this also?
281                     setMute(phone, false);
282                 }
283                 setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
284             } catch (CallStateException ex) {
285                 Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
286 
287                 if (phoneIsCdma) {
288                     // restore the cdmaPhoneCallState and bthf.cdmaSetSecondCallState:
289                     app.cdmaPhoneCallState.setCurrentCallState(
290                             app.cdmaPhoneCallState.getPreviousCallState());
291                     if (bthf != null) {
292                         bthf.cdmaSetSecondCallState(false);
293                     }
294                 }
295             }
296         }
297         return answered;
298     }
299 
300     /**
301      * Smart "hang up" helper method which hangs up exactly one connection,
302      * based on the current Phone state, as follows:
303      * <ul>
304      * <li>If there's a ringing call, hang that up.
305      * <li>Else if there's a foreground call, hang that up.
306      * <li>Else if there's a background call, hang that up.
307      * <li>Otherwise do nothing.
308      * </ul>
309      * @return true if we successfully hung up, or false
310      *              if there were no active calls at all.
311      */
hangup(Phone phone)312     static boolean hangup(Phone phone) {
313         boolean hungup = false;
314         Call ringing = phone.getRingingCall();
315         Call fg = phone.getForegroundCall();
316         Call bg = phone.getBackgroundCall();
317 
318         if (!ringing.isIdle()) {
319             if (DBG) log("HANGUP ringing call");
320             hungup = hangupRingingCall(phone);
321         } else if (!fg.isIdle()) {
322             if (DBG) log("HANGUP foreground call");
323             hungup = hangup(fg);
324         } else if (!bg.isIdle()) {
325             if (DBG) log("HANGUP background call");
326             hungup = hangup(bg);
327         }
328 
329         if (DBG) log("hungup=" + hungup);
330 
331         return hungup;
332     }
333 
hangupRingingCall(Phone phone)334     static boolean hangupRingingCall(Phone phone) {
335         if (DBG) log("hangup ringing call");
336         Call ringing = phone.getRingingCall();
337         int phoneType = phone.getPhoneType();
338 
339         if (phoneType == Phone.PHONE_TYPE_CDMA) {
340             // CDMA: Ringing call and Call waiting hangup is handled differently.
341             // For Call waiting we DO NOT call the conventional hangup(call) function
342             // as in CDMA we just want to hungup the Call waiting connection.
343             Call.State state = ringing.getState();
344             if (state == Call.State.INCOMING) {
345                 if (DBG) log("hangup ringing call");
346                 return hangup(ringing);
347             } else if (state == Call.State.WAITING) {
348                 if (DBG) log("hangup Call waiting call");
349                 final CallNotifier notifier = PhoneApp.getInstance().notifier;
350                 notifier.onCdmaCallWaitingReject();
351                 return true;
352             } else {
353                 // This should never happen cause hangupRingingCall should always be called
354                 // if the call.isRinging() returns TRUE, which basically means that the call
355                 // should either be in INCOMING or WAITING state
356                 if (DBG) log("No Ringing call to hangup");
357                 return false;
358             }
359         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
360             // GSM:  Ringing Call and Call waiting, both are hungup by calling
361             // hangup(call) function.
362             if (DBG) log("hangup ringing call");
363             return hangup(ringing);
364         } else {
365             throw new IllegalStateException("Unexpected phone type: " + phoneType);
366         }
367     }
368 
hangupActiveCall(Phone phone)369     static boolean hangupActiveCall(Phone phone) {
370         if (DBG) log("hangup active call");
371         return hangup(phone.getForegroundCall());
372     }
373 
hangupHoldingCall(Phone phone)374     static boolean hangupHoldingCall(Phone phone) {
375         if (DBG) log("hangup holding call");
376         return hangup(phone.getBackgroundCall());
377     }
378 
379     /**
380      * Used in CDMA phones to end the complete Call session
381      * @param phone the Phone object.
382      * @return true if *any* call was successfully hung up
383      */
hangupRingingAndActive(Phone phone)384     static boolean hangupRingingAndActive(Phone phone) {
385         boolean hungUpRingingCall = false;
386         boolean hungUpFgCall = false;
387         Call ringingCall = phone.getRingingCall();
388         Call fgCall = phone.getForegroundCall();
389 
390         // Hang up any Ringing Call
391         if (!ringingCall.isIdle()) {
392             if (DBG) log("endCallInternal: Hang up Ringing Call");
393             hungUpRingingCall = hangupRingingCall(phone);
394         }
395 
396         // Hang up any Active Call
397         if (!fgCall.isIdle()) {
398             if (DBG) log("endCallInternal: Hang up Foreground Call");
399             hungUpFgCall = hangupActiveCall(phone);
400         }
401 
402         return hungUpRingingCall || hungUpFgCall;
403     }
404 
405     /**
406      * Trivial wrapper around Call.hangup(), except that we return a
407      * boolean success code rather than throwing CallStateException on
408      * failure.
409      *
410      * @return true if the call was successfully hung up, or false
411      *         if the call wasn't actually active.
412      */
hangup(Call call)413     static boolean hangup(Call call) {
414         try {
415             call.hangup();
416             return true;
417         } catch (CallStateException ex) {
418             Log.e(LOG_TAG, "Call hangup: caught " + ex, ex);
419         }
420 
421         return false;
422     }
423 
424     /**
425      * Trivial wrapper around Connection.hangup(), except that we silently
426      * do nothing (rather than throwing CallStateException) if the
427      * connection wasn't actually active.
428      */
hangup(Connection c)429     static void hangup(Connection c) {
430         try {
431             if (c != null) {
432                 c.hangup();
433             }
434         } catch (CallStateException ex) {
435             Log.w(LOG_TAG, "Connection hangup: caught " + ex, ex);
436         }
437     }
438 
answerAndEndHolding(Phone phone)439     static boolean answerAndEndHolding(Phone phone) {
440         if (DBG) log("end holding & answer waiting: 1");
441         if (!hangupHoldingCall(phone)) {
442             Log.e(LOG_TAG, "end holding failed!");
443             return false;
444         }
445 
446         if (DBG) log("end holding & answer waiting: 2");
447         return answerCall(phone);
448 
449     }
450 
answerAndEndActive(Phone phone)451     static boolean answerAndEndActive(Phone phone) {
452         if (DBG) log("answerAndEndActive()...");
453 
454         // Unlike the answerCall() method, we *don't* need to stop the
455         // ringer or change audio modes here since the user is already
456         // in-call, which means that the audio mode is already set
457         // correctly, and that we wouldn't have started the ringer in the
458         // first place.
459 
460         // hanging up the active call also accepts the waiting call
461         return hangupActiveCall(phone);
462     }
463 
464     /**
465      * For a CDMA phone, advance the call state upon making a new
466      * outgoing call.
467      *
468      * <pre>
469      *   IDLE -> SINGLE_ACTIVE
470      * or
471      *   SINGLE_ACTIVE -> THRWAY_ACTIVE
472      * </pre>
473      * @param app The phone instance.
474      */
updateCdmaCallStateOnNewOutgoingCall(PhoneApp app)475     static private void updateCdmaCallStateOnNewOutgoingCall(PhoneApp app) {
476         if (app.cdmaPhoneCallState.getCurrentCallState() ==
477             CdmaPhoneCallState.PhoneCallState.IDLE) {
478             // This is the first outgoing call. Set the Phone Call State to ACTIVE
479             app.cdmaPhoneCallState.setCurrentCallState(
480                 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
481         } else {
482             // This is the second outgoing call. Set the Phone Call State to 3WAY
483             app.cdmaPhoneCallState.setCurrentCallState(
484                 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
485         }
486     }
487 
488     /**
489      * Dial the number using the phone passed in.
490      *
491      * @param phone the Phone object.
492      * @param number to be dialed as requested by the user.
493      * @param contactRef that triggered the call. Either a 'tel:' or a
494      * 'content://contacts' uri depending on how the call was
495      * initiated (dialpad vs contact).
496      * @return either CALL_STATUS_DIALED, CALL_STATUS_DIALED_MMI, or CALL_STATUS_FAILED
497      */
placeCall(Phone phone, String number, Uri contactRef)498     static int placeCall(Phone phone, String number, Uri contactRef) {
499         int status = CALL_STATUS_DIALED;
500         try {
501             if (DBG) log("placeCall: '" + number + "'...");
502 
503             Connection cn = phone.dial(number);
504             if (DBG) log("===> phone.dial() returned: " + cn);
505 
506             int phoneType = phone.getPhoneType();
507 
508             // On GSM phones, null is returned for MMI codes
509             if (cn == null) {
510                 if (phoneType == Phone.PHONE_TYPE_GSM) {
511                     if (DBG) log("dialed MMI code: " + number);
512                     status = CALL_STATUS_DIALED_MMI;
513                     // Set dialed MMI command to service
514                     if (mNwService != null) {
515                         try {
516                             mNwService.setMmiString(number);
517                             if (DBG) log("Extended NW bindService setUssdString (" + number + ")");
518                         } catch (RemoteException e) {
519                             mNwService = null;
520                         }
521                     }
522                 } else {
523                     status = PhoneUtils.CALL_STATUS_FAILED;
524                 }
525             } else {
526                 PhoneApp app = PhoneApp.getInstance();
527 
528                 if (phoneType == Phone.PHONE_TYPE_CDMA) {
529                     updateCdmaCallStateOnNewOutgoingCall(app);
530                 }
531 
532                 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
533 
534                 // phone.dial() succeeded: we're now in a normal phone call.
535                 // attach the URI to the CallerInfo Object if it is there,
536                 // otherwise just attach the Uri Reference.
537                 // if the uri does not have a "content" scheme, then we treat
538                 // it as if it does NOT have a unique reference.
539                 String content = phone.getContext().getContentResolver().SCHEME_CONTENT;
540                 if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
541                     Object userDataObject = cn.getUserData();
542                     if (userDataObject == null) {
543                         cn.setUserData(contactRef);
544                     } else {
545                         // TODO: This branch is dead code, we have
546                         // just created the connection 'cn' which has
547                         // no user data (null) by default.
548                         if (userDataObject instanceof CallerInfo) {
549                             ((CallerInfo) userDataObject).contactRefUri = contactRef;
550                         } else {
551                             ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =
552                                 contactRef;
553                         }
554                     }
555                 }
556                 setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
557             }
558         } catch (CallStateException ex) {
559             Log.w(LOG_TAG, "Exception from phone.dial()", ex);
560             status = CALL_STATUS_FAILED;
561         }
562 
563         return status;
564     }
565 
566     /**
567      * Dial the number using a 3rd party provider gateway.  Should
568      * *NOT* be called if the number is either:
569      * . An emergency one
570      * . A GSM MMI code
571      * . A CDMA feature code
572      * None of the above is  checked in this method, it's the caller's
573      * responsability to make sure the number is 'valid'.
574      *
575      * If the connection is establised, this method issues a sync call
576      * that may block to query the caller info.
577      * TODO: Change the logic to use the async query.
578      *
579      * @param phone the Phone object.
580      * @param context To perform the CallerInfo query.
581      * @param number to be dialed as requested by the user. This is
582      * NOT the phone number to connect to. It is used only to build the
583      * call card and to update the call log. See above for restrictions.
584      * @param contactRef that triggered the call. Typically a 'tel:'
585      * uri but can also be a 'content://contacts' one.
586      * @param gatewayUri Is the address used to setup the connection.
587      * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED
588      */
placeCallVia(Context context, Phone phone, String number, Uri contactRef, Uri gatewayUri)589     static int placeCallVia(Context context, Phone phone,
590                             String number, Uri contactRef, Uri gatewayUri) {
591         if (DBG) log("placeCallVia: '" + number + "' GW:'" + gatewayUri + "'");
592 
593         // TODO: 'tel' should be a contant defined in framework base
594         // somewhere (it is in webkit.)
595         if (null == gatewayUri || !"tel".equals(gatewayUri.getScheme())) {
596             Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri);
597             return CALL_STATUS_FAILED;
598         }
599 
600         // We can use getSchemeSpecificPart because we don't allow #
601         // in the gateway numbers (treated a fragment delim.) However
602         // if we allow more complex gateway numbers sequence (with
603         // passwords or whatnot) that use #, this may break.
604         // TODO: Need to support MMI codes.
605         String gatewayNumber = gatewayUri.getSchemeSpecificPart();
606         Connection connection;
607         try {
608             connection = phone.dial(gatewayNumber);
609         } catch (CallStateException ex) {
610             Log.e(LOG_TAG, "Exception dialing gateway", ex);
611             connection = null;
612         }
613 
614         if (null == connection) {
615             Log.e(LOG_TAG, "Got null connection.");
616             return CALL_STATUS_FAILED;
617         }
618 
619         PhoneApp app = PhoneApp.getInstance();
620         boolean phoneIsCdma = (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA);
621 
622         if (phoneIsCdma) {
623             updateCdmaCallStateOnNewOutgoingCall(app);
624         }
625         PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
626 
627         // Clean up the number to be displayed.
628         if (phoneIsCdma) {
629             number = CdmaConnection.formatDialString(number);
630         }
631         number = PhoneNumberUtils.extractNetworkPortion(number);
632         number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
633         number = PhoneNumberUtils.formatNumber(number);
634 
635         // Get the caller info synchronously because we need the final
636         // CallerInfo object to update the dialed number with the one
637         // requested by the user (and not the provider's gateway number).
638         CallerInfo info = null;
639 
640         if (ContentResolver.SCHEME_CONTENT.equals(contactRef.getScheme())) {
641             info = CallerInfo.getCallerInfo(context, contactRef);
642         }
643 
644         // Fallback, lookup contact using the phone number if the
645         // contact's URI scheme was not content:// or if is was but
646         // the lookup failed.
647         if (null == info) {
648             info = CallerInfo.getCallerInfo(context, number);
649         }
650         info.phoneNumber = number;
651         connection.setUserData(info);
652 
653         setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
654         return CALL_STATUS_DIALED;
655     }
656 
657     /**
658      * Wrapper function to control when to send an empty Flash command to the network.
659      * Mainly needed for CDMA networks, such as scenarios when we need to send a blank flash
660      * to the network prior to placing a 3-way call for it to be successful.
661      */
sendEmptyFlash(Phone phone)662     static void sendEmptyFlash(Phone phone) {
663         if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
664             Call fgCall = phone.getForegroundCall();
665             if (fgCall.getState() == Call.State.ACTIVE) {
666                 // Send the empty flash
667                 if (DBG) Log.d(LOG_TAG, "onReceive: (CDMA) sending empty flash to network");
668                 switchHoldingAndActive(phone);
669             }
670         }
671     }
672 
switchHoldingAndActive(Phone phone)673     static void switchHoldingAndActive(Phone phone) {
674         try {
675             if (DBG) log("switchHoldingAndActive");
676             phone.switchHoldingAndActive();
677         } catch (CallStateException ex) {
678             Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
679         }
680     }
681 
682     /**
683      * Restore the mute setting from the earliest connection of the
684      * foreground call.
685      */
restoreMuteState(Phone phone)686     static Boolean restoreMuteState(Phone phone) {
687         //get the earliest connection
688         Connection c = phone.getForegroundCall().getEarliestConnection();
689 
690         // only do this if connection is not null.
691         if (c != null) {
692 
693             int phoneType = phone.getPhoneType();
694 
695             // retrieve the mute value.
696             Boolean shouldMute = null;
697 
698             // In CDMA, mute is not maintained per Connection. Single mute apply for
699             // a call where  call can have multiple connections such as
700             // Three way and Call Waiting.  Therefore retrieving Mute state for
701             // latest connection can apply for all connection in that call
702             if (phoneType == Phone.PHONE_TYPE_CDMA) {
703                 shouldMute = sConnectionMuteTable.get(
704                         phone.getForegroundCall().getLatestConnection());
705             } else if (phoneType == Phone.PHONE_TYPE_GSM) {
706                 shouldMute = sConnectionMuteTable.get(
707                         phone.getForegroundCall().getEarliestConnection());
708             }
709             if (shouldMute == null) {
710                 if (DBG) log("problem retrieving mute value for this connection.");
711                 shouldMute = Boolean.FALSE;
712             }
713 
714             // set the mute value and return the result.
715             setMute (phone, shouldMute.booleanValue());
716             return shouldMute;
717         }
718         return Boolean.valueOf(getMute (phone));
719     }
720 
mergeCalls(Phone phone)721     static void mergeCalls(Phone phone) {
722         int phoneType = phone.getPhoneType();
723         if (phoneType == Phone.PHONE_TYPE_GSM) {
724             try {
725                 if (DBG) log("mergeCalls");
726                 phone.conference();
727             } catch (CallStateException ex) {
728                 Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
729             }
730         } else if (phoneType == Phone.PHONE_TYPE_CDMA) {
731             if (DBG) log("mergeCalls");
732             PhoneApp app = PhoneApp.getInstance();
733             if (app.cdmaPhoneCallState.getCurrentCallState()
734                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
735                 // Send flash cmd
736                 // TODO(Moto): Need to change the call from switchHoldingAndActive to
737                 // something meaningful as we are not actually trying to swap calls but
738                 // instead are merging two calls by sending a Flash command.
739                 switchHoldingAndActive(phone);
740 
741                 // Set the Phone Call State to conference
742                 app.cdmaPhoneCallState.setCurrentCallState(
743                         CdmaPhoneCallState.PhoneCallState.CONF_CALL);
744             }
745         } else {
746             throw new IllegalStateException("Unexpected phone type: " + phoneType);
747         }
748     }
749 
separateCall(Connection c)750     static void separateCall(Connection c) {
751         try {
752             if (DBG) log("separateCall: " + c.getAddress());
753             c.separate();
754         } catch (CallStateException ex) {
755             Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
756         }
757     }
758 
759     /**
760      * Handle the MMIInitiate message and put up an alert that lets
761      * the user cancel the operation, if applicable.
762      *
763      * @param context context to get strings.
764      * @param mmiCode the MmiCode object being started.
765      * @param buttonCallbackMessage message to post when button is clicked.
766      * @param previousAlert a previous alert used in this activity.
767      * @return the dialog handle
768      */
displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)769     static Dialog displayMMIInitiate(Context context,
770                                           MmiCode mmiCode,
771                                           Message buttonCallbackMessage,
772                                           Dialog previousAlert) {
773         if (DBG) log("displayMMIInitiate: " + mmiCode);
774         if (previousAlert != null) {
775             previousAlert.dismiss();
776         }
777 
778         // The UI paradigm we are using now requests that all dialogs have
779         // user interaction, and that any other messages to the user should
780         // be by way of Toasts.
781         //
782         // In adhering to this request, all MMI initiating "OK" dialogs
783         // (non-cancelable MMIs) that end up being closed when the MMI
784         // completes (thereby showing a completion dialog) are being
785         // replaced with Toasts.
786         //
787         // As a side effect, moving to Toasts for the non-cancelable MMIs
788         // also means that buttonCallbackMessage (which was tied into "OK")
789         // is no longer invokable for these dialogs.  This is not a problem
790         // since the only callback messages we supported were for cancelable
791         // MMIs anyway.
792         //
793         // A cancelable MMI is really just a USSD request. The term
794         // "cancelable" here means that we can cancel the request when the
795         // system prompts us for a response, NOT while the network is
796         // processing the MMI request.  Any request to cancel a USSD while
797         // the network is NOT ready for a response may be ignored.
798         //
799         // With this in mind, we replace the cancelable alert dialog with
800         // a progress dialog, displayed until we receive a request from
801         // the the network.  For more information, please see the comments
802         // in the displayMMIComplete() method below.
803         //
804         // Anything that is NOT a USSD request is a normal MMI request,
805         // which will bring up a toast (desribed above).
806         // Optional code for Extended USSD running prompt
807         if (mNwService != null) {
808             if (DBG) log("running USSD code, displaying indeterminate progress.");
809             // create the indeterminate progress dialog and display it.
810             ProgressDialog pd = new ProgressDialog(context);
811             CharSequence textmsg = "";
812             try {
813                 textmsg = mNwService.getMmiRunningText();
814 
815             } catch (RemoteException e) {
816                 mNwService = null;
817                 textmsg = context.getText(R.string.ussdRunning);
818             }
819             if (DBG) log("Extended NW displayMMIInitiate (" + textmsg+ ")");
820             pd.setMessage(textmsg);
821             pd.setCancelable(false);
822             pd.setIndeterminate(true);
823             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
824             pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
825             pd.show();
826             // trigger a 15 seconds timeout to clear this progress dialog
827             mMmiTimeoutCbMsg = buttonCallbackMessage;
828             try {
829                 mMmiTimeoutCbMsg.getTarget().sendMessageDelayed(buttonCallbackMessage, 15000);
830             } catch(NullPointerException e) {
831                 mMmiTimeoutCbMsg = null;
832             }
833             return pd;
834         }
835 
836         boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
837 
838         if (!isCancelable) {
839             if (DBG) log("not a USSD code, displaying status toast.");
840             CharSequence text = context.getText(R.string.mmiStarted);
841             Toast.makeText(context, text, Toast.LENGTH_SHORT)
842                 .show();
843             return null;
844         } else {
845             if (DBG) log("running USSD code, displaying indeterminate progress.");
846 
847             // create the indeterminate progress dialog and display it.
848             ProgressDialog pd = new ProgressDialog(context);
849             pd.setMessage(context.getText(R.string.ussdRunning));
850             pd.setCancelable(false);
851             pd.setIndeterminate(true);
852             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
853 
854             pd.show();
855 
856             return pd;
857         }
858 
859     }
860 
861     /**
862      * Handle the MMIComplete message and fire off an intent to display
863      * the message.
864      *
865      * @param context context to get strings.
866      * @param mmiCode MMI result.
867      * @param previousAlert a previous alert used in this activity.
868      */
displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)869     static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
870             Message dismissCallbackMessage,
871             AlertDialog previousAlert) {
872         CharSequence text;
873         int title = 0;  // title for the progress dialog, if needed.
874         MmiCode.State state = mmiCode.getState();
875 
876         if (DBG) log("displayMMIComplete: state=" + state);
877         // Clear timeout trigger message
878         if(mMmiTimeoutCbMsg != null) {
879             try{
880                 mMmiTimeoutCbMsg.getTarget().removeMessages(mMmiTimeoutCbMsg.what);
881                 if (DBG) log("Extended NW displayMMIComplete removeMsg");
882             } catch (NullPointerException e) {
883             }
884             mMmiTimeoutCbMsg = null;
885         }
886 
887 
888         switch (state) {
889             case PENDING:
890                 // USSD code asking for feedback from user.
891                 text = mmiCode.getMessage();
892                 if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
893                 break;
894             case CANCELLED:
895                 text = context.getText(R.string.mmiCancelled);
896                 break;
897             case COMPLETE:
898                 if (PhoneApp.getInstance().getPUKEntryActivity() != null) {
899                     // if an attempt to unPUK the device was made, we specify
900                     // the title and the message here.
901                     title = com.android.internal.R.string.PinMmi;
902                     text = context.getText(R.string.puk_unlocked);
903                     break;
904                 }
905                 // All other conditions for the COMPLETE mmi state will cause
906                 // the case to fall through to message logic in common with
907                 // the FAILED case.
908 
909             case FAILED:
910                 text = mmiCode.getMessage();
911                 if (DBG) log("- using text from MMI message: '" + text + "'");
912                 break;
913             default:
914                 throw new IllegalStateException("Unexpected MmiCode state: " + state);
915         }
916 
917         if (previousAlert != null) {
918             previousAlert.dismiss();
919         }
920 
921         // Check to see if a UI exists for the PUK activation.  If it does
922         // exist, then it indicates that we're trying to unblock the PUK.
923         PhoneApp app = PhoneApp.getInstance();
924         if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
925             if (DBG) log("displaying PUK unblocking progress dialog.");
926 
927             // create the progress dialog, make sure the flags and type are
928             // set correctly.
929             ProgressDialog pd = new ProgressDialog(app);
930             pd.setTitle(title);
931             pd.setMessage(text);
932             pd.setCancelable(false);
933             pd.setIndeterminate(true);
934             pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
935             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
936 
937             // display the dialog
938             pd.show();
939 
940             // indicate to the Phone app that the progress dialog has
941             // been assigned for the PUK unlock / SIM READY process.
942             app.setPukEntryProgressDialog(pd);
943 
944         } else {
945             // In case of failure to unlock, we'll need to reset the
946             // PUK unlock activity, so that the user may try again.
947             if (app.getPUKEntryActivity() != null) {
948                 app.setPukEntryActivity(null);
949             }
950 
951             // A USSD in a pending state means that it is still
952             // interacting with the user.
953             if (state != MmiCode.State.PENDING) {
954                 if (DBG) log("MMI code has finished running.");
955 
956                 // Replace response message with Extended Mmi wording
957                 if (mNwService != null) {
958                     try {
959                         text = mNwService.getUserMessage(text);
960                     } catch (RemoteException e) {
961                         mNwService = null;
962                     }
963                     if (DBG) log("Extended NW displayMMIInitiate (" + text + ")");
964                     if (text == null || text.length() == 0)
965                         return;
966                 }
967 
968                 // displaying system alert dialog on the screen instead of
969                 // using another activity to display the message.  This
970                 // places the message at the forefront of the UI.
971                 AlertDialog newDialog = new AlertDialog.Builder(context)
972                         .setMessage(text)
973                         .setPositiveButton(R.string.ok, null)
974                         .setCancelable(true)
975                         .create();
976 
977                 newDialog.getWindow().setType(
978                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
979                 newDialog.getWindow().addFlags(
980                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
981 
982                 newDialog.show();
983             } else {
984                 if (DBG) log("USSD code has requested user input. Constructing input dialog.");
985 
986                 // USSD MMI code that is interacting with the user.  The
987                 // basic set of steps is this:
988                 //   1. User enters a USSD request
989                 //   2. We recognize the request and displayMMIInitiate
990                 //      (above) creates a progress dialog.
991                 //   3. Request returns and we get a PENDING or COMPLETE
992                 //      message.
993                 //   4. These MMI messages are caught in the PhoneApp
994                 //      (onMMIComplete) and the InCallScreen
995                 //      (mHandler.handleMessage) which bring up this dialog
996                 //      and closes the original progress dialog,
997                 //      respectively.
998                 //   5. If the message is anything other than PENDING,
999                 //      we are done, and the alert dialog (directly above)
1000                 //      displays the outcome.
1001                 //   6. If the network is requesting more information from
1002                 //      the user, the MMI will be in a PENDING state, and
1003                 //      we display this dialog with the message.
1004                 //   7. User input, or cancel requests result in a return
1005                 //      to step 1.  Keep in mind that this is the only
1006                 //      time that a USSD should be canceled.
1007 
1008                 // inflate the layout with the scrolling text area for the dialog.
1009                 LayoutInflater inflater = (LayoutInflater) context.getSystemService(
1010                         Context.LAYOUT_INFLATER_SERVICE);
1011                 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
1012 
1013                 // get the input field.
1014                 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
1015 
1016                 // specify the dialog's click listener, with SEND and CANCEL logic.
1017                 final DialogInterface.OnClickListener mUSSDDialogListener =
1018                     new DialogInterface.OnClickListener() {
1019                         public void onClick(DialogInterface dialog, int whichButton) {
1020                             switch (whichButton) {
1021                                 case DialogInterface.BUTTON1:
1022                                     phone.sendUssdResponse(inputText.getText().toString());
1023                                     break;
1024                                 case DialogInterface.BUTTON2:
1025                                     if (mmiCode.isCancelable()) {
1026                                         mmiCode.cancel();
1027                                     }
1028                                     break;
1029                             }
1030                         }
1031                     };
1032 
1033                 // build the dialog
1034                 final AlertDialog newDialog = new AlertDialog.Builder(context)
1035                         .setMessage(text)
1036                         .setView(dialogView)
1037                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
1038                         .setNegativeButton(R.string.cancel, mUSSDDialogListener)
1039                         .setCancelable(false)
1040                         .create();
1041 
1042                 // attach the key listener to the dialog's input field and make
1043                 // sure focus is set.
1044                 final View.OnKeyListener mUSSDDialogInputListener =
1045                     new View.OnKeyListener() {
1046                         public boolean onKey(View v, int keyCode, KeyEvent event) {
1047                             switch (keyCode) {
1048                                 case KeyEvent.KEYCODE_CALL:
1049                                 case KeyEvent.KEYCODE_ENTER:
1050                                     phone.sendUssdResponse(inputText.getText().toString());
1051                                     newDialog.dismiss();
1052                                     return true;
1053                             }
1054                             return false;
1055                         }
1056                     };
1057                 inputText.setOnKeyListener(mUSSDDialogInputListener);
1058                 inputText.requestFocus();
1059 
1060                 // set the window properties of the dialog
1061                 newDialog.getWindow().setType(
1062                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
1063                 newDialog.getWindow().addFlags(
1064                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
1065 
1066                 // now show the dialog!
1067                 newDialog.show();
1068             }
1069         }
1070     }
1071 
1072     /**
1073      * Cancels the current pending MMI operation, if applicable.
1074      * @return true if we canceled an MMI operation, or false
1075      *         if the current pending MMI wasn't cancelable
1076      *         or if there was no current pending MMI at all.
1077      *
1078      * @see displayMMIInitiate
1079      */
cancelMmiCode(Phone phone)1080     static boolean cancelMmiCode(Phone phone) {
1081         List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
1082         int count = pendingMmis.size();
1083         if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
1084 
1085         boolean canceled = false;
1086         if (count > 0) {
1087             // assume that we only have one pending MMI operation active at a time.
1088             // I don't think it's possible to enter multiple MMI codes concurrently
1089             // in the phone UI, because during the MMI operation, an Alert panel
1090             // is displayed, which prevents more MMI code from being entered.
1091             MmiCode mmiCode = pendingMmis.get(0);
1092             if (mmiCode.isCancelable()) {
1093                 mmiCode.cancel();
1094                 canceled = true;
1095             }
1096         }
1097 
1098         //clear timeout message and pre-set MMI command
1099         if (mNwService != null) {
1100             try {
1101                 mNwService.clearMmiString();
1102             } catch (RemoteException e) {
1103                 mNwService = null;
1104             }
1105         }
1106         if (mMmiTimeoutCbMsg != null) {
1107             mMmiTimeoutCbMsg = null;
1108         }
1109         return canceled;
1110     }
1111 
1112     public static class VoiceMailNumberMissingException extends Exception {
VoiceMailNumberMissingException()1113         VoiceMailNumberMissingException() {
1114             super();
1115         }
1116 
VoiceMailNumberMissingException(String msg)1117         VoiceMailNumberMissingException(String msg) {
1118             super(msg);
1119         }
1120     }
1121 
1122     /**
1123      * Gets the phone number to be called from an intent.  Requires a Context
1124      * to access the contacts database, and a Phone to access the voicemail
1125      * number.
1126      *
1127      * <p>If <code>phone</code> is <code>null</code>, the function will return
1128      * <code>null</code> for <code>voicemail:</code> URIs;
1129      * if <code>context</code> is <code>null</code>, the function will return
1130      * <code>null</code> for person/phone URIs.</p>
1131      *
1132      * @param context a context to use (or
1133      * @param phone the phone on which the number would be called
1134      * @param intent the intent
1135      *
1136      * @throws VoiceMailNumberMissingException if <code>intent</code> contains
1137      *         a <code>voicemail:</code> URI, but <code>phone</code> does not
1138      *         have a voicemail number set.
1139      *
1140      * @return the phone number that would be called by the intent,
1141      *         or <code>null</code> if the number cannot be found.
1142      */
getNumberFromIntent(Context context, Phone phone, Intent intent)1143     static String getNumberFromIntent(Context context, Phone phone, Intent intent)
1144             throws VoiceMailNumberMissingException {
1145         final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
1146 
1147         // Check for a voicemail-dailing request.  If the voicemail number is
1148         // empty, throw a VoiceMailNumberMissingException.
1149         if (intent.getData().getScheme().equals("voicemail") &&
1150                 (number == null || TextUtils.isEmpty(number)))
1151             throw new VoiceMailNumberMissingException();
1152 
1153         return number;
1154     }
1155 
1156     /**
1157      * Returns the caller-id info corresponding to the specified Connection.
1158      * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
1159      * extract a phone number from the specified Connection, and feed that
1160      * number into CallerInfo.getCallerInfo().)
1161      *
1162      * The returned CallerInfo may be null in certain error cases, like if the
1163      * specified Connection was null, or if we weren't able to get a valid
1164      * phone number from the Connection.
1165      *
1166      * Finally, if the getCallerInfo() call did succeed, we save the resulting
1167      * CallerInfo object in the "userData" field of the Connection.
1168      *
1169      * NOTE: This API should be avoided, with preference given to the
1170      * asynchronous startGetCallerInfo API.
1171      */
getCallerInfo(Context context, Connection c)1172     static CallerInfo getCallerInfo(Context context, Connection c) {
1173         CallerInfo info = null;
1174 
1175         if (c != null) {
1176             //See if there is a URI attached.  If there is, this means
1177             //that there is no CallerInfo queried yet, so we'll need to
1178             //replace the URI with a full CallerInfo object.
1179             Object userDataObject = c.getUserData();
1180             if (userDataObject instanceof Uri) {
1181                 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
1182                 if (info != null) {
1183                     c.setUserData(info);
1184                 }
1185             } else {
1186                 if (userDataObject instanceof CallerInfoToken) {
1187                     //temporary result, while query is running
1188                     info = ((CallerInfoToken) userDataObject).currentInfo;
1189                 } else {
1190                     //final query result
1191                     info = (CallerInfo) userDataObject;
1192                 }
1193                 if (info == null) {
1194                     // No URI, or Existing CallerInfo, so we'll have to make do with
1195                     // querying a new CallerInfo using the connection's phone number.
1196                     String number = c.getAddress();
1197 
1198                     if (DBG) log("getCallerInfo: number = " + number);
1199 
1200                     if (!TextUtils.isEmpty(number)) {
1201                         info = CallerInfo.getCallerInfo(context, number);
1202                         if (info != null) {
1203                             c.setUserData(info);
1204                         }
1205                     }
1206                 }
1207             }
1208         }
1209         return info;
1210     }
1211 
1212     /**
1213      * Class returned by the startGetCallerInfo call to package a temporary
1214      * CallerInfo Object, to be superceded by the CallerInfo Object passed
1215      * into the listener when the query with token mAsyncQueryToken is complete.
1216      */
1217     public static class CallerInfoToken {
1218         /**indicates that there will no longer be updates to this request.*/
1219         public boolean isFinal;
1220 
1221         public CallerInfo currentInfo;
1222         public CallerInfoAsyncQuery asyncQuery;
1223     }
1224 
1225     /**
1226      * Start a CallerInfo Query based on the earliest connection in the call.
1227      */
startGetCallerInfo(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1228     static CallerInfoToken startGetCallerInfo(Context context, Call call,
1229             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1230         PhoneApp app = PhoneApp.getInstance();
1231         Connection conn = null;
1232         int phoneType = app.phone.getPhoneType();
1233         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1234             conn = call.getLatestConnection();
1235         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1236             conn = call.getEarliestConnection();
1237         } else {
1238             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1239         }
1240 
1241         return startGetCallerInfo(context, conn, listener, cookie);
1242     }
1243 
1244     /**
1245      * place a temporary callerinfo object in the hands of the caller and notify
1246      * caller when the actual query is done.
1247      */
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1248     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1249             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1250         CallerInfoToken cit;
1251 
1252         if (c == null) {
1253             //TODO: perhaps throw an exception here.
1254             cit = new CallerInfoToken();
1255             cit.asyncQuery = null;
1256             return cit;
1257         }
1258 
1259         // There are now 3 states for the userdata.
1260         //   1. Uri - query has not been executed yet
1261         //   2. CallerInfoToken - query is executing, but has not completed.
1262         //   3. CallerInfo - query has executed.
1263         // In each case we have slightly different behaviour:
1264         //   1. If the query has not been executed yet (Uri or null), we start
1265         //      query execution asynchronously, and note it by attaching a
1266         //      CallerInfoToken as the userData.
1267         //   2. If the query is executing (CallerInfoToken), we've essentially
1268         //      reached a state where we've received multiple requests for the
1269         //      same callerInfo.  That means that once the query is complete,
1270         //      we'll need to execute the additional listener requested.
1271         //   3. If the query has already been executed (CallerInfo), we just
1272         //      return the CallerInfo object as expected.
1273         //   4. Regarding isFinal - there are cases where the CallerInfo object
1274         //      will not be attached, like when the number is empty (caller id
1275         //      blocking).  This flag is used to indicate that the
1276         //      CallerInfoToken object is going to be permanent since no
1277         //      query results will be returned.  In the case where a query
1278         //      has been completed, this flag is used to indicate to the caller
1279         //      that the data will not be updated since it is valid.
1280         //
1281         //      Note: For the case where a number is NOT retrievable, we leave
1282         //      the CallerInfo as null in the CallerInfoToken.  This is
1283         //      something of a departure from the original code, since the old
1284         //      code manufactured a CallerInfo object regardless of the query
1285         //      outcome.  From now on, we will append an empty CallerInfo
1286         //      object, to mirror previous behaviour, and to avoid Null Pointer
1287         //      Exceptions.
1288         Object userDataObject = c.getUserData();
1289         if (userDataObject instanceof Uri) {
1290             //create a dummy callerinfo, populate with what we know from URI.
1291             cit = new CallerInfoToken();
1292             cit.currentInfo = new CallerInfo();
1293             cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1294                     (Uri) userDataObject, sCallerInfoQueryListener, c);
1295             cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1296             cit.isFinal = false;
1297 
1298             c.setUserData(cit);
1299 
1300             if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
1301 
1302         } else if (userDataObject == null) {
1303             // No URI, or Existing CallerInfo, so we'll have to make do with
1304             // querying a new CallerInfo using the connection's phone number.
1305             String number = c.getAddress();
1306 
1307             cit = new CallerInfoToken();
1308             cit.currentInfo = new CallerInfo();
1309 
1310             // Store CNAP information retrieved from the Connection (we want to do this
1311             // here regardless of whether the number is empty or not).
1312             cit.currentInfo.cnapName =  c.getCnapName();
1313             cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
1314                                                              // by ContactInfo later
1315             cit.currentInfo.numberPresentation = c.getNumberPresentation();
1316             cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1317 
1318             if (DBG) log("startGetCallerInfo: number = " + number);
1319             if (DBG) log("startGetCallerInfo: CNAP Info from FW(1): name="
1320                     + cit.currentInfo.cnapName
1321                     + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1322 
1323             // handling case where number is null (caller id hidden) as well.
1324             if (!TextUtils.isEmpty(number)) {
1325                 // Check for special CNAP cases and modify the CallerInfo accordingly
1326                 // to be sure we keep the right information to display/log later
1327                 number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
1328                         cit.currentInfo.numberPresentation);
1329 
1330                 cit.currentInfo.phoneNumber = number;
1331                 // For scenarios where we may receive a valid number from the network but a
1332                 // restricted/unavailable presentation, we do not want to perform a contact query
1333                 // (see note on isFinal above). So we set isFinal to true here as well.
1334                 if (cit.currentInfo.numberPresentation != Connection.PRESENTATION_ALLOWED) {
1335                     cit.isFinal = true;
1336                 } else {
1337                     cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1338                             number, sCallerInfoQueryListener, c);
1339                     cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1340                     cit.isFinal = false;
1341                 }
1342             } else {
1343                 // This is the case where we are querying on a number that
1344                 // is null or empty, like a caller whose caller id is
1345                 // blocked or empty (CLIR).  The previous behaviour was to
1346                 // throw a null CallerInfo object back to the user, but
1347                 // this departure is somewhat cleaner.
1348                 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
1349                 cit.isFinal = true; // please see note on isFinal, above.
1350             }
1351 
1352             c.setUserData(cit);
1353 
1354             if (DBG) log("startGetCallerInfo: query based on number: " + number);
1355 
1356         } else if (userDataObject instanceof CallerInfoToken) {
1357             // query is running, just tack on this listener to the queue.
1358             cit = (CallerInfoToken) userDataObject;
1359 
1360             // handling case where number is null (caller id hidden) as well.
1361             if (cit.asyncQuery != null) {
1362                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1363 
1364                 if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
1365                         listener.getClass().toString());
1366             } else {
1367                 // handling case where number/name gets updated later on by the network
1368                 String updatedNumber = c.getAddress();
1369                 if (DBG) log("startGetCallerInfo: updatedNumber initially = " + updatedNumber);
1370                 if (!TextUtils.isEmpty(updatedNumber)) {
1371                     // Store CNAP information retrieved from the Connection
1372                     cit.currentInfo.cnapName =  c.getCnapName();
1373                     // This can still get overwritten by ContactInfo
1374                     cit.currentInfo.name = cit.currentInfo.cnapName;
1375                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1376                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1377 
1378                     updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
1379                             updatedNumber, cit.currentInfo.numberPresentation);
1380 
1381                     cit.currentInfo.phoneNumber = updatedNumber;
1382                     if (DBG) log("startGetCallerInfo: updatedNumber=" + updatedNumber);
1383                     if (DBG) log("startGetCallerInfo: CNAP Info from FW(2): name="
1384                             + cit.currentInfo.cnapName
1385                             + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1386                     // For scenarios where we may receive a valid number from the network but a
1387                     // restricted/unavailable presentation, we do not want to perform a contact query
1388                     // (see note on isFinal above). So we set isFinal to true here as well.
1389                     if (cit.currentInfo.numberPresentation != Connection.PRESENTATION_ALLOWED) {
1390                         cit.isFinal = true;
1391                     } else {
1392                         cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1393                                 updatedNumber, sCallerInfoQueryListener, c);
1394                         cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1395                         cit.isFinal = false;
1396                     }
1397                 } else {
1398                     if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
1399                     if (cit.currentInfo == null) {
1400                         cit.currentInfo = new CallerInfo();
1401                     }
1402                     // Store CNAP information retrieved from the Connection
1403                     cit.currentInfo.cnapName = c.getCnapName();  // This can still get
1404                                                                  // overwritten by ContactInfo
1405                     cit.currentInfo.name = cit.currentInfo.cnapName;
1406                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1407                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1408 
1409                     if (DBG) log("startGetCallerInfo: CNAP Info from FW(3): name="
1410                             + cit.currentInfo.cnapName
1411                             + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1412                     cit.isFinal = true; // please see note on isFinal, above.
1413                 }
1414             }
1415         } else {
1416             cit = new CallerInfoToken();
1417             cit.currentInfo = (CallerInfo) userDataObject;
1418             cit.asyncQuery = null;
1419             cit.isFinal = true;
1420             // since the query is already done, call the listener.
1421             if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
1422         }
1423         return cit;
1424     }
1425 
1426     /**
1427      * Implemented for CallerInfo.OnCallerInfoQueryCompleteListener interface.
1428      * Updates the connection's userData when called.
1429      */
1430     private static final int QUERY_TOKEN = -1;
1431     static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
1432         new CallerInfoAsyncQuery.OnQueryCompleteListener () {
1433             public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
1434                 if (DBG) log("query complete, updating connection.userdata");
1435                 Connection conn = (Connection) cookie;
1436 
1437                 // Added a check if CallerInfo is coming from ContactInfo or from Connection.
1438                 // If no ContactInfo, then we want to use CNAP information coming from network
1439                 if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
1440                 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
1441                     // If the number presentation has not been set by
1442                     // the ContactInfo, use the one from the
1443                     // connection.
1444 
1445                     // TODO: Need a new util method to merge the info
1446                     // from the Connection in a CallerInfo object.
1447                     // Here 'ci' is a new CallerInfo instance read
1448                     // from the DB. It has lost all the connection
1449                     // info preset before the query (see PhoneUtils
1450                     // line 1334). We should have a method to merge
1451                     // back into this new instance the info from the
1452                     // connection object not set by the DB. If the
1453                     // Connection already has a CallerInfo instance in
1454                     // userData, then we could use this instance to
1455                     // fill 'ci' in. The same routine could be used in
1456                     // PhoneUtils.
1457                     if (0 == ci.numberPresentation) {
1458                         ci.numberPresentation = conn.getNumberPresentation();
1459                     }
1460                 } else {
1461                     CallerInfo newCi = getCallerInfo(null, conn);
1462                     if (newCi != null) {
1463                         newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1464                         ci = newCi;
1465                     }
1466                 }
1467                 conn.setUserData(ci);
1468             }
1469         };
1470 
1471 
1472     /**
1473      * Returns a single "name" for the specified given a CallerInfo object.
1474      * If the name is null, return defaultString as the default value, usually
1475      * context.getString(R.string.unknown).
1476      */
getCompactNameFromCallerInfo(CallerInfo ci, Context context)1477     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1478         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1479 
1480         String compactName = null;
1481         if (ci != null) {
1482             compactName = ci.name;
1483             if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1484                 compactName = ci.phoneNumber;
1485             }
1486             // Perform any modifications for special CNAP cases to the name being displayed,
1487             // if applicable.
1488             compactName = modifyForSpecialCnapCases(context, ci, compactName, ci.numberPresentation);
1489         }
1490 
1491         if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1492             // If we're still null/empty here, then check if we have a presentation
1493             // string that takes precedence that we could return, otherwise display
1494             // "unknown" string.
1495             if (ci != null && ci.numberPresentation == Connection.PRESENTATION_RESTRICTED) {
1496                 compactName = context.getString(R.string.private_num);
1497             } else if (ci != null && ci.numberPresentation == Connection.PRESENTATION_PAYPHONE) {
1498                 compactName = context.getString(R.string.payphone);
1499             } else {
1500                 compactName = context.getString(R.string.unknown);
1501             }
1502         }
1503         if (DBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
1504         return compactName;
1505     }
1506 
1507     /**
1508      * Returns true if the specified Call is a "conference call", meaning
1509      * that it owns more than one Connection object.  This information is
1510      * used to trigger certain UI changes that appear when a conference
1511      * call is active (like displaying the label "Conference call", and
1512      * enabling the "Manage conference" UI.)
1513      *
1514      * Watch out: This method simply checks the number of Connections,
1515      * *not* their states.  So if a Call has (for example) one ACTIVE
1516      * connection and one DISCONNECTED connection, this method will return
1517      * true (which is unintuitive, since the Call isn't *really* a
1518      * conference call any more.)
1519      *
1520      * @return true if the specified call has more than one connection (in any state.)
1521      */
isConferenceCall(Call call)1522     static boolean isConferenceCall(Call call) {
1523         // CDMA phones don't have the same concept of "conference call" as
1524         // GSM phones do; there's no special "conference call" state of
1525         // the UI or a "manage conference" function.  (Instead, when
1526         // you're in a 3-way call, all we can do is display the "generic"
1527         // state of the UI.)  So as far as the in-call UI is concerned,
1528         // Conference corresponds to generic display.
1529         PhoneApp app = PhoneApp.getInstance();
1530         int phoneType = app.phone.getPhoneType();
1531         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1532             CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
1533             if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL)
1534                     || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1535                     && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) {
1536                 return true;
1537             }
1538         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1539             List<Connection> connections = call.getConnections();
1540             if (connections != null && connections.size() > 1) {
1541                 return true;
1542             }
1543         } else {
1544             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1545         }
1546         return false;
1547 
1548         // TODO: We may still want to change the semantics of this method
1549         // to say that a given call is only really a conference call if
1550         // the number of ACTIVE connections, not the total number of
1551         // connections, is greater than one.  (See warning comment in the
1552         // javadoc above.)
1553         // Here's an implementation of that:
1554         //        if (connections == null) {
1555         //            return false;
1556         //        }
1557         //        int numActiveConnections = 0;
1558         //        for (Connection conn : connections) {
1559         //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1560         //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1561         //            if (numActiveConnections > 1) {
1562         //                return true;
1563         //            }
1564         //        }
1565         //        return false;
1566     }
1567 
1568     /**
1569      * Launch the Dialer to start a new call.
1570      * This is just a wrapper around the ACTION_DIAL intent.
1571      */
startNewCall(final Phone phone)1572     static void startNewCall(final Phone phone) {
1573         final KeyguardManager keyguardManager = PhoneApp.getInstance().getKeyguardManager();
1574         if (!keyguardManager.inKeyguardRestrictedInputMode()) {
1575             internalStartNewCall(phone);
1576         } else {
1577             keyguardManager.exitKeyguardSecurely(new KeyguardManager.OnKeyguardExitResult() {
1578                 public void onKeyguardExitResult(boolean success) {
1579                     if (success) {
1580                         internalStartNewCall(phone);
1581                     }
1582                 }
1583             });
1584         }
1585     }
1586 
internalStartNewCall(Phone phone)1587     private static void internalStartNewCall(Phone phone) {
1588         // Sanity-check that this is OK given the current state of the phone.
1589         if (!okToAddCall(phone)) {
1590             Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1591             dumpCallState(phone);
1592             return;
1593         }
1594 
1595         // if applicable, mute the call while we're showing the add call UI.
1596         if (!phone.getForegroundCall().isIdle()) {
1597             setMuteInternal(phone, true);
1598             // Inform the phone app that this mute state was NOT done
1599             // voluntarily by the User.
1600             PhoneApp.getInstance().setRestoreMuteOnInCallResume(true);
1601         }
1602 
1603         Intent intent = new Intent(Intent.ACTION_DIAL);
1604         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1605 
1606         // when we request the dialer come up, we also want to inform
1607         // it that we're going through the "add call" option from the
1608         // InCallScreen / PhoneUtils.
1609         intent.putExtra(ADD_CALL_MODE_KEY, true);
1610 
1611         PhoneApp.getInstance().startActivity(intent);
1612     }
1613 
1614     /**
1615      * Brings up the UI used to handle an incoming call.
1616      *
1617      * Originally, this brought up an IncomingCallPanel instance
1618      * (which was a subclass of Dialog) on top of whatever app
1619      * was currently running.  Now, we take you directly to the
1620      * in-call screen, whose CallCard automatically does the right
1621      * thing if there's a Call that's currently ringing.
1622      */
showIncomingCallUi()1623     static void showIncomingCallUi() {
1624         if (DBG) log("showIncomingCallUi()...");
1625         PhoneApp app = PhoneApp.getInstance();
1626 
1627         // Before bringing up the "incoming call" UI, force any system
1628         // dialogs (like "recent tasks" or the power dialog) to close first.
1629         try {
1630             ActivityManagerNative.getDefault().closeSystemDialogs("call");
1631         } catch (RemoteException e) {
1632         }
1633 
1634         // Go directly to the in-call screen.
1635         // (No need to do anything special if we're already on the in-call
1636         // screen; it'll notice the phone state change and update itself.)
1637 
1638         // But first, grab a full wake lock.  We do this here, before we
1639         // even fire off the InCallScreen intent, to make sure the
1640         // ActivityManager doesn't try to pause the InCallScreen as soon
1641         // as it comes up.  (See bug 1648751.)
1642         //
1643         // And since the InCallScreen isn't visible yet (we haven't even
1644         // fired off the intent yet), we DON'T want the screen to actually
1645         // come on right now.  So *before* acquiring the wake lock we need
1646         // to call preventScreenOn(), which tells the PowerManager that
1647         // the screen should stay off even if someone's holding a full
1648         // wake lock.  (This prevents any flicker during the "incoming
1649         // call" sequence.  The corresponding preventScreenOn(false) call
1650         // will come from the InCallScreen when it's finally ready to be
1651         // displayed.)
1652         //
1653         // TODO: this is all a temporary workaround.  The real fix is to add
1654         // an Activity attribute saying "this Activity wants to wake up the
1655         // phone when it's displayed"; that way the ActivityManager could
1656         // manage the wake locks *and* arrange for the screen to come on at
1657         // the exact moment that the InCallScreen is ready to be displayed.
1658         // (See bug 1648751.)
1659         app.preventScreenOn(true);
1660         app.requestWakeState(PhoneApp.WakeState.FULL);
1661 
1662         // Fire off the InCallScreen intent.
1663         app.displayCallScreen();
1664     }
1665 
turnOnSpeaker(Context context, boolean flag, boolean store)1666     static void turnOnSpeaker(Context context, boolean flag, boolean store) {
1667         if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
1668         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1669 
1670         audioManager.setSpeakerphoneOn(flag);
1671         // record the speaker-enable value
1672         if (store) {
1673             sIsSpeakerEnabled = flag;
1674         }
1675         if (flag) {
1676             NotificationMgr.getDefault().notifySpeakerphone();
1677         } else {
1678             NotificationMgr.getDefault().cancelSpeakerphone();
1679         }
1680 
1681         // We also need to make a fresh call to PhoneApp.updateWakeState()
1682         // any time the speaker state changes, since the screen timeout is
1683         // sometimes different depending on whether or not the speaker is
1684         // in use.
1685         PhoneApp app = PhoneApp.getInstance();
1686         app.updateWakeState();
1687 
1688         // Update the Proximity sensor based on speaker state
1689         app.updateProximitySensorMode(app.phone.getState());
1690     }
1691 
1692     /**
1693      * Restore the speaker mode, called after a wired headset disconnect
1694      * event.
1695      */
restoreSpeakerMode(Context context)1696     static void restoreSpeakerMode(Context context) {
1697         if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1698 
1699         // change the mode if needed.
1700         if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1701             turnOnSpeaker(context, sIsSpeakerEnabled, false);
1702         }
1703     }
1704 
isSpeakerOn(Context context)1705     static boolean isSpeakerOn(Context context) {
1706         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1707         return audioManager.isSpeakerphoneOn();
1708     }
1709 
1710 
turnOnNoiseSuppression(Context context, boolean flag, boolean store)1711     static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) {
1712         if (DBG) log("turnOnNoiseSuppression: " + flag);
1713         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1714 
1715         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1716             return;
1717         }
1718 
1719         if (flag) {
1720             audioManager.setParameters("noise_suppression=auto");
1721         } else {
1722             audioManager.setParameters("noise_suppression=off");
1723         }
1724 
1725         // record the speaker-enable value
1726         if (store) {
1727             sIsNoiseSuppressionEnabled = flag;
1728         }
1729 
1730         // TODO: implement and manage ICON
1731 
1732     }
1733 
restoreNoiseSuppression(Context context)1734     static void restoreNoiseSuppression(Context context) {
1735         if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled);
1736 
1737         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1738             return;
1739         }
1740 
1741         // change the mode if needed.
1742         if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) {
1743             turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false);
1744         }
1745     }
1746 
isNoiseSuppressionOn(Context context)1747     static boolean isNoiseSuppressionOn(Context context) {
1748 
1749         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1750             return false;
1751         }
1752 
1753         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1754         String noiseSuppression = audioManager.getParameters("noise_suppression");
1755         if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression);
1756         if (noiseSuppression.contains("off")) {
1757             return false;
1758         } else {
1759             return true;
1760         }
1761     }
1762 
1763     /**
1764      * Wrapper around Phone.setMute() that also updates the mute icon in
1765      * the status bar.
1766      *
1767      * All muting / unmuting from the in-call UI should go through this
1768      * wrapper.
1769      */
setMute(Phone phone, boolean muted)1770     static void setMute(Phone phone, boolean muted) {
1771         // make the call to mute the audio
1772         setMuteInternal(phone, muted);
1773 
1774         // update the foreground connections to match.  This includes
1775         // all the connections on conference calls.
1776         for (Connection cn : phone.getForegroundCall().getConnections()) {
1777             if (sConnectionMuteTable.get(cn) == null) {
1778                 if (DBG) log("problem retrieving mute value for this connection.");
1779             }
1780             sConnectionMuteTable.put(cn, Boolean.valueOf(muted));
1781         }
1782     }
1783 
1784     /**
1785      * Internally used muting function.  All UI calls should use {@link setMute}
1786      */
setMuteInternal(Phone phone, boolean muted)1787     static void setMuteInternal(Phone phone, boolean muted) {
1788         if (DBG) log("setMute: " + muted);
1789         Context context = phone.getContext();
1790         boolean routeToAudioManager =
1791             context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
1792         if (routeToAudioManager) {
1793             AudioManager audioManager =
1794                 (AudioManager) phone.getContext().getSystemService(Context.AUDIO_SERVICE);
1795             if (DBG) log(" setMicrophoneMute: " + muted);
1796             audioManager.setMicrophoneMute(muted);
1797         } else {
1798             phone.setMute(muted);
1799         }
1800         if (muted) {
1801             NotificationMgr.getDefault().notifyMute();
1802         } else {
1803             NotificationMgr.getDefault().cancelMute();
1804         }
1805     }
1806 
getMute(Phone phone)1807     static boolean getMute(Phone phone) {
1808         Context context = phone.getContext();
1809         boolean routeToAudioManager =
1810             context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
1811         if (routeToAudioManager) {
1812             AudioManager audioManager =
1813                 (AudioManager) phone.getContext().getSystemService(Context.AUDIO_SERVICE);
1814             return audioManager.isMicrophoneMute();
1815         } else {
1816             return phone.getMute();
1817         }
1818     }
1819 
1820     /**
1821      * A really simple wrapper around AudioManager.setMode(),
1822      * with a bit of extra logging to help debug the exact
1823      * timing (and call stacks) for all our setMode() calls.
1824      *
1825      * Also, add additional state monitoring to determine
1826      * whether or not certain calls to change the audio mode
1827      * are ignored.
1828      */
setAudioMode(Context context, int mode)1829     /* package */ static void setAudioMode(Context context, int mode) {
1830         if (DBG) Log.d(LOG_TAG, "setAudioMode(" + audioModeToString(mode) + ")...");
1831 
1832         //decide whether or not to ignore the audio setting
1833         boolean ignore = false;
1834 
1835         switch (sAudioBehaviourState) {
1836             case AUDIO_RINGING:
1837                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_IN_CALL));
1838                 break;
1839             case AUDIO_OFFHOOK:
1840                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_RINGTONE));
1841                 break;
1842             case AUDIO_IDLE:
1843             default:
1844                 ignore = (mode == AudioManager.MODE_IN_CALL);
1845                 break;
1846         }
1847 
1848         if (!ignore) {
1849             AudioManager audioManager =
1850                     (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1851             // Enable stack dump only when actively debugging ("new Throwable()" is expensive!)
1852             if (DBG_SETAUDIOMODE_STACK) Log.d(LOG_TAG, "Stack:", new Throwable("stack dump"));
1853             audioManager.setMode(mode);
1854         } else {
1855             if (DBG) Log.d(LOG_TAG, "setAudioMode(), state is " + sAudioBehaviourState +
1856                     " ignoring " + audioModeToString(mode) + " request");
1857         }
1858     }
audioModeToString(int mode)1859     private static String audioModeToString(int mode) {
1860         switch (mode) {
1861             case AudioManager.MODE_INVALID: return "MODE_INVALID";
1862             case AudioManager.MODE_CURRENT: return "MODE_CURRENT";
1863             case AudioManager.MODE_NORMAL: return "MODE_NORMAL";
1864             case AudioManager.MODE_RINGTONE: return "MODE_RINGTONE";
1865             case AudioManager.MODE_IN_CALL: return "MODE_IN_CALL";
1866             default: return String.valueOf(mode);
1867         }
1868     }
1869 
1870     /**
1871      * Handles the wired headset button while in-call.
1872      *
1873      * This is called from the PhoneApp, not from the InCallScreen,
1874      * since the HEADSETHOOK button means "mute or unmute the current
1875      * call" *any* time a call is active, even if the user isn't actually
1876      * on the in-call screen.
1877      *
1878      * @return true if we consumed the event.
1879      */
handleHeadsetHook(Phone phone)1880     /* package */ static boolean handleHeadsetHook(Phone phone) {
1881         if (DBG) log("handleHeadsetHook()...");
1882 
1883         // If the phone is totally idle, we ignore HEADSETHOOK events
1884         // (and instead let them fall through to the media player.)
1885         if (phone.getState() == Phone.State.IDLE) {
1886             return false;
1887         }
1888 
1889         // Ok, the phone is in use.
1890         // The headset button button means "Answer" if an incoming call is
1891         // ringing.  If not, it toggles the mute / unmute state.
1892         //
1893         // And in any case we *always* consume this event; this means
1894         // that the usual mediaplayer-related behavior of the headset
1895         // button will NEVER happen while the user is on a call.
1896 
1897         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1898         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1899         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1900 
1901         if (hasRingingCall) {
1902             // If an incoming call is ringing, answer it (just like with the
1903             // CALL button):
1904             int phoneType = phone.getPhoneType();
1905             if (phoneType == Phone.PHONE_TYPE_CDMA) {
1906                 answerCall(phone);
1907             } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1908                 if (hasActiveCall && hasHoldingCall) {
1909                     if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
1910                     answerAndEndActive(phone);
1911                 } else {
1912                     if (DBG) log("handleHeadsetHook: ringing ==> answer!");
1913                     answerCall(phone);  // Automatically holds the current active call,
1914                                         // if there is one
1915                 }
1916             } else {
1917                 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1918             }
1919         } else {
1920             // No incoming ringing call.
1921             // If it is NOT an emg #, toggle the mute state. Otherwise, ignore the hook.
1922             Connection c = phone.getForegroundCall().getLatestConnection();
1923             if (c != null && !PhoneNumberUtils.isEmergencyNumber(c.getAddress())) {
1924                 if (getMute(phone)) {
1925                     if (DBG) log("handleHeadsetHook: UNmuting...");
1926                     setMute(phone, false);
1927                 } else {
1928                     if (DBG) log("handleHeadsetHook: muting...");
1929                     setMute(phone, true);
1930                 }
1931             }
1932         }
1933 
1934         // Even if the InCallScreen is the current activity, there's no
1935         // need to force it to update, because (1) if we answered a
1936         // ringing call, the InCallScreen will imminently get a phone
1937         // state change event (causing an update), and (2) if we muted or
1938         // unmuted, the setMute() call automagically updates the status
1939         // bar, and there's no "mute" indication in the InCallScreen
1940         // itself (other than the menu item, which only ever stays
1941         // onscreen for a second anyway.)
1942         // TODO: (2) isn't entirely true anymore. Once we return our result
1943         // to the PhoneApp, we ask InCallScreen to update its control widgets
1944         // in case we changed mute or speaker state and phones with touch-
1945         // screen [toggle] buttons need to update themselves.
1946 
1947         return true;
1948     }
1949 
1950     /**
1951      * Look for ANY connections on the phone that qualify as being
1952      * disconnected.
1953      *
1954      * @return true if we find a connection that is disconnected over
1955      * all the phone's call objects.
1956      */
hasDisconnectedConnections(Phone phone)1957     /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1958         return hasDisconnectedConnections(phone.getForegroundCall()) ||
1959                 hasDisconnectedConnections(phone.getBackgroundCall()) ||
1960                 hasDisconnectedConnections(phone.getRingingCall());
1961     }
1962 
1963     /**
1964      * Iterate over all connections in a call to see if there are any
1965      * that are not alive (disconnected or idle).
1966      *
1967      * @return true if we find a connection that is disconnected, and
1968      * pending removal via
1969      * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}.
1970      */
hasDisconnectedConnections(Call call)1971     private static final boolean hasDisconnectedConnections(Call call) {
1972         // look through all connections for non-active ones.
1973         for (Connection c : call.getConnections()) {
1974             if (!c.isAlive()) {
1975                 return true;
1976             }
1977         }
1978         return false;
1979     }
1980 
1981     //
1982     // Misc UI policy helper functions
1983     //
1984 
1985     /**
1986      * @return true if we're allowed to swap calls, given the current
1987      * state of the Phone.
1988      */
okToSwapCalls(Phone phone)1989     /* package */ static boolean okToSwapCalls(Phone phone) {
1990         int phoneType = phone.getPhoneType();
1991         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1992             // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
1993             // state by either accepting a Call Waiting or by merging two calls
1994             PhoneApp app = PhoneApp.getInstance();
1995             return (app.cdmaPhoneCallState.getCurrentCallState()
1996                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
1997         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1998             // GSM: "Swap" is available if both lines are in use and there's no
1999             // incoming call.  (Actually we need to verify that the active
2000             // call really is in the ACTIVE state and the holding call really
2001             // is in the HOLDING state, since you *can't* actually swap calls
2002             // when the foreground call is DIALING or ALERTING.)
2003             return phone.getRingingCall().isIdle()
2004                     && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
2005                     && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
2006         } else {
2007             throw new IllegalStateException("Unexpected phone type: " + phoneType);
2008         }
2009     }
2010 
2011     /**
2012      * @return true if we're allowed to merge calls, given the current
2013      * state of the Phone.
2014      */
okToMergeCalls(Phone phone)2015     /* package */ static boolean okToMergeCalls(Phone phone) {
2016         int phoneType = phone.getPhoneType();
2017         if (phoneType == Phone.PHONE_TYPE_CDMA) {
2018             // CDMA: "Merge" is enabled only when the user is in a 3Way call.
2019             PhoneApp app = PhoneApp.getInstance();
2020             return ((app.cdmaPhoneCallState.getCurrentCallState()
2021                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
2022                     && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
2023         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
2024             // GSM: "Merge" is available if both lines are in use and there's no
2025             // incoming call, *and* the current conference isn't already
2026             // "full".
2027             return phone.getRingingCall().isIdle() && phone.canConference();
2028         } else {
2029             throw new IllegalStateException("Unexpected phone type: " + phoneType);
2030         }
2031     }
2032 
2033     /**
2034      * @return true if the UI should let you add a new call, given the current
2035      * state of the Phone.
2036      */
okToAddCall(Phone phone)2037     /* package */ static boolean okToAddCall(Phone phone) {
2038         int phoneType = phone.getPhoneType();
2039         final Call.State fgCallState = phone.getForegroundCall().getState();
2040         if (phoneType == Phone.PHONE_TYPE_CDMA) {
2041            // CDMA: "Add call" menu item is only enabled when the call is in
2042            // - ForegroundCall is in ACTIVE state
2043            // - After 30 seconds of user Ignoring/Missing a Call Waiting call.
2044             PhoneApp app = PhoneApp.getInstance();
2045             return ((fgCallState == Call.State.ACTIVE)
2046                     && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
2047         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
2048             // GSM: "Add call" is available only if ALL of the following are true:
2049             // - There's no incoming ringing call
2050             // - There's < 2 lines in use
2051             // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
2052             //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
2053             final boolean hasRingingCall = !phone.getRingingCall().isIdle();
2054             final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
2055             final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
2056             final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2057 
2058             return !hasRingingCall
2059                     && !allLinesTaken
2060                     && ((fgCallState == Call.State.ACTIVE)
2061                         || (fgCallState == Call.State.IDLE)
2062                         || (fgCallState == Call.State.DISCONNECTED));
2063         } else {
2064             throw new IllegalStateException("Unexpected phone type: " + phoneType);
2065         }
2066     }
2067 
2068     /**
2069      * Based on the input CNAP number string,
2070      * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
2071      * Otherwise, return CNAP_SPECIAL_CASE_NO.
2072      */
checkCnapSpecialCases(String n)2073     private static int checkCnapSpecialCases(String n) {
2074         if (n.equals("PRIVATE") ||
2075                 n.equals("P") ||
2076                 n.equals("RES")) {
2077             if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
2078             return Connection.PRESENTATION_RESTRICTED;
2079         } else if (n.equals("UNAVAILABLE") ||
2080                 n.equals("UNKNOWN") ||
2081                 n.equals("UNA") ||
2082                 n.equals("U")) {
2083             if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
2084             return Connection.PRESENTATION_UNKNOWN;
2085         } else {
2086             if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
2087             return CNAP_SPECIAL_CASE_NO;
2088         }
2089     }
2090 
2091     /**
2092      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
2093      * from the network to indicate different number presentations, convert them to
2094      * expected number and presentation values within the CallerInfo object.
2095      * @param number number we use to verify if we are in a corner case
2096      * @param presentation presentation value used to verify if we are in a corner case
2097      * @return the new String that should be used for the phone number
2098      */
modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)2099     /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
2100             String number, int presentation) {
2101         // Obviously we return number if ci == null, but still return number if
2102         // number == null, because in these cases the correct string will still be
2103         // displayed/logged after this function returns based on the presentation value.
2104         if (ci == null || number == null) return number;
2105 
2106         if (DBG) log("modifyForSpecialCnapCases: initially, number=" + number
2107                 + ", presentation=" + presentation);
2108 
2109         // "ABSENT NUMBER" is a possible value we could get from the network as the
2110         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
2111         // and fix the presentation to be the same.
2112         if (number.equals(context.getString(R.string.absent_num))
2113                 && presentation == Connection.PRESENTATION_ALLOWED) {
2114             number = context.getString(R.string.unknown);
2115             ci.numberPresentation = Connection.PRESENTATION_UNKNOWN;
2116         }
2117 
2118         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
2119         // cases only apply if we received an allowed presentation from the network, so check
2120         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
2121         // match the presentation passed in for verification (meaning we changed it previously
2122         // because it's a corner case and we're being called from a different entry point).
2123         if (ci.numberPresentation == Connection.PRESENTATION_ALLOWED
2124                 || (ci.numberPresentation != presentation
2125                         && presentation == Connection.PRESENTATION_ALLOWED)) {
2126             int cnapSpecialCase = checkCnapSpecialCases(number);
2127             if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
2128                 // For all special strings, change number & numberPresentation.
2129                 if (cnapSpecialCase == Connection.PRESENTATION_RESTRICTED) {
2130                     number = context.getString(R.string.private_num);
2131                 } else if (cnapSpecialCase == Connection.PRESENTATION_UNKNOWN) {
2132                     number = context.getString(R.string.unknown);
2133                 }
2134                 if (DBG) log("SpecialCnap: number=" + number
2135                         + "; presentation now=" + cnapSpecialCase);
2136                 ci.numberPresentation = cnapSpecialCase;
2137             }
2138         }
2139         if (DBG) log("modifyForSpecialCnapCases: returning number string=" + number);
2140         return number;
2141     }
2142 
2143     //
2144     // Support for 3rd party phone service providers.
2145     //
2146 
2147     /**
2148      * Check if all the provider's info is present in the intent.
2149      * @param intent Expected to have the provider's extra.
2150      * @return true if the intent has all the extras to build the
2151      * in-call screen's provider info overlay.
2152      */
hasPhoneProviderExtras(Intent intent)2153     /* package */ static boolean hasPhoneProviderExtras(Intent intent) {
2154         if (null == intent) {
2155             return false;
2156         }
2157         final String name = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
2158         final String gatewayUri = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI);
2159 
2160         return !TextUtils.isEmpty(name) && !TextUtils.isEmpty(gatewayUri);
2161     }
2162 
2163     /**
2164      * Copy all the expected extras set when a 3rd party provider is
2165      * used from the source intent to the destination one.  Checks all
2166      * the required extras are present, if any is missing, none will
2167      * be copied.
2168      * @param src Intent which may contain the provider's extras.
2169      * @param dst Intent where a copy of the extras will be added if applicable.
2170      */
checkAndCopyPhoneProviderExtras(Intent src, Intent dst)2171     /* package */ static void checkAndCopyPhoneProviderExtras(Intent src, Intent dst) {
2172         if (!hasPhoneProviderExtras(src)) {
2173             Log.d(LOG_TAG, "checkAndCopyPhoneProviderExtras: some or all extras are missing.");
2174             return;
2175         }
2176 
2177         dst.putExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE,
2178                      src.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE));
2179         dst.putExtra(InCallScreen.EXTRA_GATEWAY_URI,
2180                      src.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI));
2181     }
2182 
2183     /**
2184      * Get the provider's label from the intent.
2185      * @param context to lookup the provider's package name.
2186      * @param intent with an extra set to the provider's package name.
2187      * @return The provider's application label. null if an error
2188      * occurred during the lookup of the package name or the label.
2189      */
getProviderLabel(Context context, Intent intent)2190     /* package */ static CharSequence getProviderLabel(Context context, Intent intent) {
2191         String packageName = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
2192         PackageManager pm = context.getPackageManager();
2193 
2194         try {
2195             ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
2196 
2197             return pm.getApplicationLabel(info);
2198         } catch (PackageManager.NameNotFoundException e) {
2199             return null;
2200         }
2201     }
2202 
2203     /**
2204      * Get the provider's icon.
2205      * @param context to lookup the provider's icon.
2206      * @param intent with an extra set to the provider's package name.
2207      * @return The provider's application icon. null if an error occured during the icon lookup.
2208      */
getProviderIcon(Context context, Intent intent)2209     /* package */ static Drawable getProviderIcon(Context context, Intent intent) {
2210         String packageName = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
2211         PackageManager pm = context.getPackageManager();
2212 
2213         try {
2214             return pm.getApplicationIcon(packageName);
2215         } catch (PackageManager.NameNotFoundException e) {
2216             return null;
2217         }
2218     }
2219 
2220     /**
2221      * Return the gateway uri from the intent.
2222      * @param intent With the gateway uri extra.
2223      * @return The gateway URI or null if not found.
2224      */
getProviderGatewayUri(Intent intent)2225     /* package */ static Uri getProviderGatewayUri(Intent intent) {
2226         String uri = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI);
2227         return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
2228     }
2229 
2230     /**
2231      * Return a formatted version of the uri's scheme specific
2232      * part. E.g for 'tel:12345678', return '1-234-5678'.
2233      * @param uri A 'tel:' URI with the gateway phone number.
2234      * @return the provider's address (from the gateway uri) formatted
2235      * for user display. null if uri was null or its scheme was not 'tel:'.
2236      */
formatProviderUri(Uri uri)2237     /* package */ static String formatProviderUri(Uri uri) {
2238         if (null != uri) {
2239             if ("tel".equals(uri.getScheme())) {
2240                 return PhoneNumberUtils.formatNumber(uri.getSchemeSpecificPart());
2241             } else {
2242                 return uri.toString();
2243             }
2244         }
2245         return null;
2246     }
2247 
2248     /**
2249      * Check if a phone number can be route through a 3rd party
2250      * gateway. The number must be a global phone number in numerical
2251      * form (1-800-666-SEXY won't work).
2252      *
2253      * MMI codes and the like cannot be used as a dial number for the
2254      * gateway either.
2255      *
2256      * @param number To be dialed via a 3rd party gateway.
2257      * @return true If the number can be routed through the 3rd party network.
2258      */
isRoutableViaGateway(String number)2259     /* package */ static boolean isRoutableViaGateway(String number) {
2260         if (TextUtils.isEmpty(number)) {
2261             return false;
2262         }
2263         number = PhoneNumberUtils.stripSeparators(number);
2264         if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) {
2265             return false;
2266         }
2267         number = PhoneNumberUtils.extractNetworkPortion(number);
2268         return PhoneNumberUtils.isGlobalPhoneNumber(number);
2269     }
2270 
2271     //
2272     // General phone and call state debugging/testing code
2273     //
2274 
dumpCallState(Phone phone)2275     /* package */ static void dumpCallState(Phone phone) {
2276         PhoneApp app = PhoneApp.getInstance();
2277         Log.d(LOG_TAG, "dumpCallState():");
2278         Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
2279               + ", state = " + phone.getState());
2280 
2281         StringBuilder b = new StringBuilder(128);
2282 
2283         Call call = phone.getForegroundCall();
2284         b.setLength(0);
2285         b.append("  - FG call: ").append(call.getState());
2286         b.append(" isAlive ").append(call.getState().isAlive());
2287         b.append(" isRinging ").append(call.getState().isRinging());
2288         b.append(" isDialing ").append(call.getState().isDialing());
2289         b.append(" isIdle ").append(call.isIdle());
2290         b.append(" hasConnections ").append(call.hasConnections());
2291         Log.d(LOG_TAG, b.toString());
2292 
2293         call = phone.getBackgroundCall();
2294         b.setLength(0);
2295         b.append("  - BG call: ").append(call.getState());
2296         b.append(" isAlive ").append(call.getState().isAlive());
2297         b.append(" isRinging ").append(call.getState().isRinging());
2298         b.append(" isDialing ").append(call.getState().isDialing());
2299         b.append(" isIdle ").append(call.isIdle());
2300         b.append(" hasConnections ").append(call.hasConnections());
2301         Log.d(LOG_TAG, b.toString());
2302 
2303         call = phone.getRingingCall();
2304         b.setLength(0);
2305         b.append("  - RINGING call: ").append(call.getState());
2306         b.append(" isAlive ").append(call.getState().isAlive());
2307         b.append(" isRinging ").append(call.getState().isRinging());
2308         b.append(" isDialing ").append(call.getState().isDialing());
2309         b.append(" isIdle ").append(call.isIdle());
2310         b.append(" hasConnections ").append(call.hasConnections());
2311         Log.d(LOG_TAG, b.toString());
2312 
2313 
2314         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
2315         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
2316         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
2317         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2318         b.setLength(0);
2319         b.append("  - hasRingingCall ").append(hasRingingCall);
2320         b.append(" hasActiveCall ").append(hasActiveCall);
2321         b.append(" hasHoldingCall ").append(hasHoldingCall);
2322         b.append(" allLinesTaken ").append(allLinesTaken);
2323         Log.d(LOG_TAG, b.toString());
2324 
2325         // On CDMA phones, dump out the CdmaPhoneCallState too:
2326         if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
2327             if (app.cdmaPhoneCallState != null) {
2328                 Log.d(LOG_TAG, "  - CDMA call state: "
2329                       + app.cdmaPhoneCallState.getCurrentCallState());
2330             } else {
2331                 Log.d(LOG_TAG, "  - CDMA device, but null cdmaPhoneCallState!");
2332             }
2333         }
2334 
2335         // Watch out: the isRinging() call below does NOT tell us anything
2336         // about the state of the telephony layer; it merely tells us whether
2337         // the Ringer manager is currently playing the ringtone.
2338         boolean ringing = app.getRinger().isRinging();
2339         Log.d(LOG_TAG, "  - Ringer state: " + ringing);
2340     }
2341 
log(String msg)2342     private static void log(String msg) {
2343         Log.d(LOG_TAG, msg);
2344     }
2345 }
2346