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