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