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