• 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     public static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
90     public static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
91     public 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         log("displayMMIInitiate: " + android.telecom.Log.pii(mmiCode.toString()));
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             log("displayMMIInitiate: 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             log("displayMMIInitiate: running USSD code, displaying intermediate 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         log("displayMMIComplete: state=" + state);
866 
867         switch (state) {
868             case PENDING:
869                 // USSD code asking for feedback from user.
870                 text = mmiCode.getMessage();
871                 log("displayMMIComplete: 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                 log("displayMMIComplete (failed): 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                 log("displayMMIComplete: MMI code has finished running.");
933 
934                 log("displayMMIComplete: 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_SYSTEM_ALERT);
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                 log("displayMMIComplete: USSD code has requested user input. Constructing input "
970                         + "dialog.");
971 
972                 // USSD MMI code that is interacting with the user.  The
973                 // basic set of steps is this:
974                 //   1. User enters a USSD request
975                 //   2. We recognize the request and displayMMIInitiate
976                 //      (above) creates a progress dialog.
977                 //   3. Request returns and we get a PENDING or COMPLETE
978                 //      message.
979                 //   4. These MMI messages are caught in the PhoneApp
980                 //      (onMMIComplete) and the InCallScreen
981                 //      (mHandler.handleMessage) which bring up this dialog
982                 //      and closes the original progress dialog,
983                 //      respectively.
984                 //   5. If the message is anything other than PENDING,
985                 //      we are done, and the alert dialog (directly above)
986                 //      displays the outcome.
987                 //   6. If the network is requesting more information from
988                 //      the user, the MMI will be in a PENDING state, and
989                 //      we display this dialog with the message.
990                 //   7. User input, or cancel requests result in a return
991                 //      to step 1.  Keep in mind that this is the only
992                 //      time that a USSD should be canceled.
993 
994                 // inflate the layout with the scrolling text area for the dialog.
995                 ContextThemeWrapper contextThemeWrapper =
996                         new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme);
997                 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService(
998                         Context.LAYOUT_INFLATER_SERVICE);
999                 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
1000 
1001                 // get the input field.
1002                 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
1003 
1004                 // specify the dialog's click listener, with SEND and CANCEL logic.
1005                 final DialogInterface.OnClickListener mUSSDDialogListener =
1006                     new DialogInterface.OnClickListener() {
1007                         public void onClick(DialogInterface dialog, int whichButton) {
1008                             switch (whichButton) {
1009                                 case DialogInterface.BUTTON_POSITIVE:
1010                                     // As per spec 24.080, valid length of ussd string
1011                                     // is 1 - 160. If length is out of the range then
1012                                     // display toast message & Cancel MMI operation.
1013                                     if (inputText.length() < MIN_USSD_LEN
1014                                             || inputText.length() > MAX_USSD_LEN) {
1015                                         Toast.makeText(app,
1016                                                 app.getResources().getString(R.string.enter_input,
1017                                                 MIN_USSD_LEN, MAX_USSD_LEN),
1018                                                 Toast.LENGTH_LONG).show();
1019                                         if (mmiCode.isCancelable()) {
1020                                             mmiCode.cancel();
1021                                         }
1022                                     } else {
1023                                         phone.sendUssdResponse(inputText.getText().toString());
1024                                     }
1025                                     break;
1026                                 case DialogInterface.BUTTON_NEGATIVE:
1027                                     if (mmiCode.isCancelable()) {
1028                                         mmiCode.cancel();
1029                                     }
1030                                     break;
1031                             }
1032                         }
1033                     };
1034 
1035                 // build the dialog
1036                 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper)
1037                         .setMessage(text)
1038                         .setView(dialogView)
1039                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
1040                         .setNegativeButton(R.string.cancel, mUSSDDialogListener)
1041                         .setCancelable(false)
1042                         .create();
1043 
1044                 // attach the key listener to the dialog's input field and make
1045                 // sure focus is set.
1046                 final View.OnKeyListener mUSSDDialogInputListener =
1047                     new View.OnKeyListener() {
1048                         public boolean onKey(View v, int keyCode, KeyEvent event) {
1049                             switch (keyCode) {
1050                                 case KeyEvent.KEYCODE_CALL:
1051                                 case KeyEvent.KEYCODE_ENTER:
1052                                     if(event.getAction() == KeyEvent.ACTION_DOWN) {
1053                                         phone.sendUssdResponse(inputText.getText().toString());
1054                                         newDialog.dismiss();
1055                                     }
1056                                     return true;
1057                             }
1058                             return false;
1059                         }
1060                     };
1061                 inputText.setOnKeyListener(mUSSDDialogInputListener);
1062                 inputText.requestFocus();
1063 
1064                 // set the window properties of the dialog
1065                 newDialog.getWindow().setType(
1066                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
1067                 newDialog.getWindow().addFlags(
1068                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
1069 
1070                 // now show the dialog!
1071                 newDialog.show();
1072 
1073                 newDialog.getButton(DialogInterface.BUTTON_POSITIVE)
1074                         .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
1075                 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE)
1076                         .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
1077             }
1078         }
1079     }
1080 
1081     /**
1082      * Cancels the current pending MMI operation, if applicable.
1083      * @return true if we canceled an MMI operation, or false
1084      *         if the current pending MMI wasn't cancelable
1085      *         or if there was no current pending MMI at all.
1086      *
1087      * @see displayMMIInitiate
1088      */
cancelMmiCode(Phone phone)1089     static boolean cancelMmiCode(Phone phone) {
1090         List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
1091         int count = pendingMmis.size();
1092         if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
1093 
1094         boolean canceled = false;
1095         if (count > 0) {
1096             // assume that we only have one pending MMI operation active at a time.
1097             // I don't think it's possible to enter multiple MMI codes concurrently
1098             // in the phone UI, because during the MMI operation, an Alert panel
1099             // is displayed, which prevents more MMI code from being entered.
1100             MmiCode mmiCode = pendingMmis.get(0);
1101             if (mmiCode.isCancelable()) {
1102                 mmiCode.cancel();
1103                 canceled = true;
1104             }
1105         }
1106         return canceled;
1107     }
1108 
1109     public static class VoiceMailNumberMissingException extends Exception {
VoiceMailNumberMissingException()1110         VoiceMailNumberMissingException() {
1111             super();
1112         }
1113 
VoiceMailNumberMissingException(String msg)1114         VoiceMailNumberMissingException(String msg) {
1115             super(msg);
1116         }
1117     }
1118 
1119     /**
1120      * Given an Intent (which is presumably the ACTION_CALL intent that
1121      * initiated this outgoing call), figure out the actual phone number we
1122      * should dial.
1123      *
1124      * Note that the returned "number" may actually be a SIP address,
1125      * if the specified intent contains a sip: URI.
1126      *
1127      * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(),
1128      * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra.
1129      * (That extra, if present, tells us the exact string to pass down to the
1130      * telephony layer.  It's guaranteed to be safe to dial: it's either a PSTN
1131      * phone number with separators and keypad letters stripped out, or a raw
1132      * unencoded SIP address.)
1133      *
1134      * @return the phone number corresponding to the specified Intent, or null
1135      *   if the Intent has no action or if the intent's data is malformed or
1136      *   missing.
1137      *
1138      * @throws VoiceMailNumberMissingException if the intent
1139      *   contains a "voicemail" URI, but there's no voicemail
1140      *   number configured on the device.
1141      */
getInitialNumber(Intent intent)1142     public static String getInitialNumber(Intent intent)
1143             throws PhoneUtils.VoiceMailNumberMissingException {
1144         if (DBG) log("getInitialNumber(): " + intent);
1145 
1146         String action = intent.getAction();
1147         if (TextUtils.isEmpty(action)) {
1148             return null;
1149         }
1150 
1151         // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone
1152         // number from there.  (That extra takes precedence over the actual data
1153         // included in the intent.)
1154         if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) {
1155             String actualNumberToDial =
1156                     intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL);
1157             if (DBG) {
1158                 log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '"
1159                         + toLogSafePhoneNumber(actualNumberToDial) + "'");
1160             }
1161             return actualNumberToDial;
1162         }
1163 
1164         return getNumberFromIntent(PhoneGlobals.getInstance(), intent);
1165     }
1166 
1167     /**
1168      * Gets the phone number to be called from an intent.  Requires a Context
1169      * to access the contacts database, and a Phone to access the voicemail
1170      * number.
1171      *
1172      * <p>If <code>phone</code> is <code>null</code>, the function will return
1173      * <code>null</code> for <code>voicemail:</code> URIs;
1174      * if <code>context</code> is <code>null</code>, the function will return
1175      * <code>null</code> for person/phone URIs.</p>
1176      *
1177      * <p>If the intent contains a <code>sip:</code> URI, the returned
1178      * "number" is actually the SIP address.
1179      *
1180      * @param context a context to use (or
1181      * @param intent the intent
1182      *
1183      * @throws VoiceMailNumberMissingException if <code>intent</code> contains
1184      *         a <code>voicemail:</code> URI, but <code>phone</code> does not
1185      *         have a voicemail number set.
1186      *
1187      * @return the phone number (or SIP address) that would be called by the intent,
1188      *         or <code>null</code> if the number cannot be found.
1189      */
getNumberFromIntent(Context context, Intent intent)1190     private static String getNumberFromIntent(Context context, Intent intent)
1191             throws VoiceMailNumberMissingException {
1192         Uri uri = intent.getData();
1193         String scheme = uri.getScheme();
1194 
1195         // The sip: scheme is simple: just treat the rest of the URI as a
1196         // SIP address.
1197         if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
1198             return uri.getSchemeSpecificPart();
1199         }
1200 
1201         // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle
1202         // the other cases (i.e. tel: and voicemail: and contact: URIs.)
1203 
1204         final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
1205 
1206         // Check for a voicemail-dialing request.  If the voicemail number is
1207         // empty, throw a VoiceMailNumberMissingException.
1208         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) &&
1209                 (number == null || TextUtils.isEmpty(number)))
1210             throw new VoiceMailNumberMissingException();
1211 
1212         return number;
1213     }
1214 
1215     /**
1216      * Returns the caller-id info corresponding to the specified Connection.
1217      * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
1218      * extract a phone number from the specified Connection, and feed that
1219      * number into CallerInfo.getCallerInfo().)
1220      *
1221      * The returned CallerInfo may be null in certain error cases, like if the
1222      * specified Connection was null, or if we weren't able to get a valid
1223      * phone number from the Connection.
1224      *
1225      * Finally, if the getCallerInfo() call did succeed, we save the resulting
1226      * CallerInfo object in the "userData" field of the Connection.
1227      *
1228      * NOTE: This API should be avoided, with preference given to the
1229      * asynchronous startGetCallerInfo API.
1230      */
getCallerInfo(Context context, Connection c)1231     static CallerInfo getCallerInfo(Context context, Connection c) {
1232         CallerInfo info = null;
1233 
1234         if (c != null) {
1235             //See if there is a URI attached.  If there is, this means
1236             //that there is no CallerInfo queried yet, so we'll need to
1237             //replace the URI with a full CallerInfo object.
1238             Object userDataObject = c.getUserData();
1239             if (userDataObject instanceof Uri) {
1240                 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
1241                 if (info != null) {
1242                     c.setUserData(info);
1243                 }
1244             } else {
1245                 if (userDataObject instanceof CallerInfoToken) {
1246                     //temporary result, while query is running
1247                     info = ((CallerInfoToken) userDataObject).currentInfo;
1248                 } else {
1249                     //final query result
1250                     info = (CallerInfo) userDataObject;
1251                 }
1252                 if (info == null) {
1253                     // No URI, or Existing CallerInfo, so we'll have to make do with
1254                     // querying a new CallerInfo using the connection's phone number.
1255                     String number = c.getAddress();
1256 
1257                     if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number));
1258 
1259                     if (!TextUtils.isEmpty(number)) {
1260                         info = CallerInfo.getCallerInfo(context, number);
1261                         if (info != null) {
1262                             c.setUserData(info);
1263                         }
1264                     }
1265                 }
1266             }
1267         }
1268         return info;
1269     }
1270 
1271     /**
1272      * Class returned by the startGetCallerInfo call to package a temporary
1273      * CallerInfo Object, to be superceded by the CallerInfo Object passed
1274      * into the listener when the query with token mAsyncQueryToken is complete.
1275      */
1276     public static class CallerInfoToken {
1277         /**indicates that there will no longer be updates to this request.*/
1278         public boolean isFinal;
1279 
1280         public CallerInfo currentInfo;
1281         public CallerInfoAsyncQuery asyncQuery;
1282     }
1283 
1284     /**
1285      * Start a CallerInfo Query based on the earliest connection in the call.
1286      */
startGetCallerInfo(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1287     static CallerInfoToken startGetCallerInfo(Context context, Call call,
1288             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1289         Connection conn = null;
1290         int phoneType = call.getPhone().getPhoneType();
1291         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1292             conn = call.getLatestConnection();
1293         } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1294                 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
1295                 || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
1296                 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
1297             conn = call.getEarliestConnection();
1298         } else {
1299             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1300         }
1301 
1302         return startGetCallerInfo(context, conn, listener, cookie);
1303     }
1304 
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1305     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1306             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1307         return startGetCallerInfo(context, c, listener, cookie, null);
1308     }
1309 
1310     /**
1311      * place a temporary callerinfo object in the hands of the caller and notify
1312      * caller when the actual query is done.
1313      */
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)1314     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1315             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie,
1316             RawGatewayInfo info) {
1317         CallerInfoToken cit;
1318 
1319         if (c == null) {
1320             //TODO: perhaps throw an exception here.
1321             cit = new CallerInfoToken();
1322             cit.asyncQuery = null;
1323             return cit;
1324         }
1325 
1326         Object userDataObject = c.getUserData();
1327 
1328         // There are now 3 states for the Connection's userData object:
1329         //
1330         //   (1) Uri - query has not been executed yet
1331         //
1332         //   (2) CallerInfoToken - query is executing, but has not completed.
1333         //
1334         //   (3) CallerInfo - query has executed.
1335         //
1336         // In each case we have slightly different behaviour:
1337         //   1. If the query has not been executed yet (Uri or null), we start
1338         //      query execution asynchronously, and note it by attaching a
1339         //      CallerInfoToken as the userData.
1340         //   2. If the query is executing (CallerInfoToken), we've essentially
1341         //      reached a state where we've received multiple requests for the
1342         //      same callerInfo.  That means that once the query is complete,
1343         //      we'll need to execute the additional listener requested.
1344         //   3. If the query has already been executed (CallerInfo), we just
1345         //      return the CallerInfo object as expected.
1346         //   4. Regarding isFinal - there are cases where the CallerInfo object
1347         //      will not be attached, like when the number is empty (caller id
1348         //      blocking).  This flag is used to indicate that the
1349         //      CallerInfoToken object is going to be permanent since no
1350         //      query results will be returned.  In the case where a query
1351         //      has been completed, this flag is used to indicate to the caller
1352         //      that the data will not be updated since it is valid.
1353         //
1354         //      Note: For the case where a number is NOT retrievable, we leave
1355         //      the CallerInfo as null in the CallerInfoToken.  This is
1356         //      something of a departure from the original code, since the old
1357         //      code manufactured a CallerInfo object regardless of the query
1358         //      outcome.  From now on, we will append an empty CallerInfo
1359         //      object, to mirror previous behaviour, and to avoid Null Pointer
1360         //      Exceptions.
1361 
1362         if (userDataObject instanceof Uri) {
1363             // State (1): query has not been executed yet
1364 
1365             //create a dummy callerinfo, populate with what we know from URI.
1366             cit = new CallerInfoToken();
1367             cit.currentInfo = new CallerInfo();
1368             cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1369                     (Uri) userDataObject, sCallerInfoQueryListener, c);
1370             cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1371             cit.isFinal = false;
1372 
1373             c.setUserData(cit);
1374 
1375             if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
1376 
1377         } else if (userDataObject == null) {
1378             // No URI, or Existing CallerInfo, so we'll have to make do with
1379             // querying a new CallerInfo using the connection's phone number.
1380             String number = c.getAddress();
1381 
1382             if (info != null && info != CallGatewayManager.EMPTY_INFO) {
1383                 // Gateway number, the connection number is actually the gateway number.
1384                 // need to lookup via dialed number.
1385                 number = info.trueNumber;
1386             }
1387 
1388             if (DBG) {
1389                 log("PhoneUtils.startGetCallerInfo: new query for phone number...");
1390                 log("- number (address): " + toLogSafePhoneNumber(number));
1391                 log("- c: " + c);
1392                 log("- phone: " + c.getCall().getPhone());
1393                 int phoneType = c.getCall().getPhone().getPhoneType();
1394                 log("- phoneType: " + phoneType);
1395                 switch (phoneType) {
1396                     case PhoneConstants.PHONE_TYPE_NONE: log("  ==> PHONE_TYPE_NONE"); break;
1397                     case PhoneConstants.PHONE_TYPE_GSM: log("  ==> PHONE_TYPE_GSM"); break;
1398                     case PhoneConstants.PHONE_TYPE_IMS: log("  ==> PHONE_TYPE_IMS"); break;
1399                     case PhoneConstants.PHONE_TYPE_CDMA: log("  ==> PHONE_TYPE_CDMA"); break;
1400                     case PhoneConstants.PHONE_TYPE_SIP: log("  ==> PHONE_TYPE_SIP"); break;
1401                     case PhoneConstants.PHONE_TYPE_THIRD_PARTY:
1402                         log("  ==> PHONE_TYPE_THIRD_PARTY");
1403                         break;
1404                     default: log("  ==> Unknown phone type"); break;
1405                 }
1406             }
1407 
1408             cit = new CallerInfoToken();
1409             cit.currentInfo = new CallerInfo();
1410 
1411             // Store CNAP information retrieved from the Connection (we want to do this
1412             // here regardless of whether the number is empty or not).
1413             cit.currentInfo.cnapName =  c.getCnapName();
1414             cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
1415                                                              // by ContactInfo later
1416             cit.currentInfo.numberPresentation = c.getNumberPresentation();
1417             cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1418 
1419             if (VDBG) {
1420                 log("startGetCallerInfo: number = " + number);
1421                 log("startGetCallerInfo: CNAP Info from FW(1): name="
1422                     + cit.currentInfo.cnapName
1423                     + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1424             }
1425 
1426             // handling case where number is null (caller id hidden) as well.
1427             if (!TextUtils.isEmpty(number)) {
1428                 // Check for special CNAP cases and modify the CallerInfo accordingly
1429                 // to be sure we keep the right information to display/log later
1430                 number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
1431                         cit.currentInfo.numberPresentation);
1432 
1433                 cit.currentInfo.phoneNumber = number;
1434                 // For scenarios where we may receive a valid number from the network but a
1435                 // restricted/unavailable presentation, we do not want to perform a contact query
1436                 // (see note on isFinal above). So we set isFinal to true here as well.
1437                 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1438                     cit.isFinal = true;
1439                 } else {
1440                     if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()...");
1441                     cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1442                             number, sCallerInfoQueryListener, c);
1443                     cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1444                     cit.isFinal = false;
1445                 }
1446             } else {
1447                 // This is the case where we are querying on a number that
1448                 // is null or empty, like a caller whose caller id is
1449                 // blocked or empty (CLIR).  The previous behaviour was to
1450                 // throw a null CallerInfo object back to the user, but
1451                 // this departure is somewhat cleaner.
1452                 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
1453                 cit.isFinal = true; // please see note on isFinal, above.
1454             }
1455 
1456             c.setUserData(cit);
1457 
1458             if (DBG) {
1459                 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number));
1460             }
1461 
1462         } else if (userDataObject instanceof CallerInfoToken) {
1463             // State (2): query is executing, but has not completed.
1464 
1465             // just tack on this listener to the queue.
1466             cit = (CallerInfoToken) userDataObject;
1467 
1468             // handling case where number is null (caller id hidden) as well.
1469             if (cit.asyncQuery != null) {
1470                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1471 
1472                 if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
1473                         listener.getClass().toString());
1474             } else {
1475                 // handling case where number/name gets updated later on by the network
1476                 String updatedNumber = c.getAddress();
1477 
1478                 if (info != null) {
1479                     // Gateway number, the connection number is actually the gateway number.
1480                     // need to lookup via dialed number.
1481                     updatedNumber = info.trueNumber;
1482                 }
1483 
1484                 if (DBG) {
1485                     log("startGetCallerInfo: updatedNumber initially = "
1486                             + toLogSafePhoneNumber(updatedNumber));
1487                 }
1488                 if (!TextUtils.isEmpty(updatedNumber)) {
1489                     // Store CNAP information retrieved from the Connection
1490                     cit.currentInfo.cnapName =  c.getCnapName();
1491                     // This can still get overwritten by ContactInfo
1492                     cit.currentInfo.name = cit.currentInfo.cnapName;
1493                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1494                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1495 
1496                     updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
1497                             updatedNumber, cit.currentInfo.numberPresentation);
1498 
1499                     cit.currentInfo.phoneNumber = updatedNumber;
1500                     if (DBG) {
1501                         log("startGetCallerInfo: updatedNumber="
1502                                 + toLogSafePhoneNumber(updatedNumber));
1503                     }
1504                     if (VDBG) {
1505                         log("startGetCallerInfo: CNAP Info from FW(2): name="
1506                                 + cit.currentInfo.cnapName
1507                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1508                     } else if (DBG) {
1509                         log("startGetCallerInfo: CNAP Info from FW(2)");
1510                     }
1511                     // For scenarios where we may receive a valid number from the network but a
1512                     // restricted/unavailable presentation, we do not want to perform a contact query
1513                     // (see note on isFinal above). So we set isFinal to true here as well.
1514                     if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1515                         cit.isFinal = true;
1516                     } else {
1517                         cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1518                                 updatedNumber, sCallerInfoQueryListener, c);
1519                         cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1520                         cit.isFinal = false;
1521                     }
1522                 } else {
1523                     if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
1524                     if (cit.currentInfo == null) {
1525                         cit.currentInfo = new CallerInfo();
1526                     }
1527                     // Store CNAP information retrieved from the Connection
1528                     cit.currentInfo.cnapName = c.getCnapName();  // This can still get
1529                                                                  // overwritten by ContactInfo
1530                     cit.currentInfo.name = cit.currentInfo.cnapName;
1531                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1532                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1533 
1534                     if (VDBG) {
1535                         log("startGetCallerInfo: CNAP Info from FW(3): name="
1536                                 + cit.currentInfo.cnapName
1537                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1538                     } else if (DBG) {
1539                         log("startGetCallerInfo: CNAP Info from FW(3)");
1540                     }
1541                     cit.isFinal = true; // please see note on isFinal, above.
1542                 }
1543             }
1544         } else {
1545             // State (3): query is complete.
1546 
1547             // The connection's userDataObject is a full-fledged
1548             // CallerInfo instance.  Wrap it in a CallerInfoToken and
1549             // return it to the user.
1550 
1551             cit = new CallerInfoToken();
1552             cit.currentInfo = (CallerInfo) userDataObject;
1553             cit.asyncQuery = null;
1554             cit.isFinal = true;
1555             // since the query is already done, call the listener.
1556             if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
1557             if (DBG) log("==> cit.currentInfo = " + cit.currentInfo);
1558         }
1559         return cit;
1560     }
1561 
1562     /**
1563      * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that
1564      * we use with all our CallerInfoAsyncQuery.startQuery() requests.
1565      */
1566     private static final int QUERY_TOKEN = -1;
1567     static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
1568         new CallerInfoAsyncQuery.OnQueryCompleteListener () {
1569             /**
1570              * When the query completes, we stash the resulting CallerInfo
1571              * object away in the Connection's "userData" (where it will
1572              * later be retrieved by the in-call UI.)
1573              */
1574             public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
1575                 if (DBG) log("query complete, updating connection.userdata");
1576                 Connection conn = (Connection) cookie;
1577 
1578                 // Added a check if CallerInfo is coming from ContactInfo or from Connection.
1579                 // If no ContactInfo, then we want to use CNAP information coming from network
1580                 if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
1581                 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
1582                     // If the number presentation has not been set by
1583                     // the ContactInfo, use the one from the
1584                     // connection.
1585 
1586                     // TODO: Need a new util method to merge the info
1587                     // from the Connection in a CallerInfo object.
1588                     // Here 'ci' is a new CallerInfo instance read
1589                     // from the DB. It has lost all the connection
1590                     // info preset before the query (see PhoneUtils
1591                     // line 1334). We should have a method to merge
1592                     // back into this new instance the info from the
1593                     // connection object not set by the DB. If the
1594                     // Connection already has a CallerInfo instance in
1595                     // userData, then we could use this instance to
1596                     // fill 'ci' in. The same routine could be used in
1597                     // PhoneUtils.
1598                     if (0 == ci.numberPresentation) {
1599                         ci.numberPresentation = conn.getNumberPresentation();
1600                     }
1601                 } else {
1602                     // No matching contact was found for this number.
1603                     // Return a new CallerInfo based solely on the CNAP
1604                     // information from the network.
1605 
1606                     CallerInfo newCi = getCallerInfo(null, conn);
1607 
1608                     // ...but copy over the (few) things we care about
1609                     // from the original CallerInfo object:
1610                     if (newCi != null) {
1611                         newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1612                         newCi.geoDescription = ci.geoDescription; // To get geo description string
1613                         ci = newCi;
1614                     }
1615                 }
1616 
1617                 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection...");
1618                 conn.setUserData(ci);
1619             }
1620         };
1621 
1622 
1623     /**
1624      * Returns a single "name" for the specified given a CallerInfo object.
1625      * If the name is null, return defaultString as the default value, usually
1626      * context.getString(R.string.unknown).
1627      */
getCompactNameFromCallerInfo(CallerInfo ci, Context context)1628     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1629         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1630 
1631         String compactName = null;
1632         if (ci != null) {
1633             if (TextUtils.isEmpty(ci.name)) {
1634                 // Perform any modifications for special CNAP cases to
1635                 // the phone number being displayed, if applicable.
1636                 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber,
1637                                                         ci.numberPresentation);
1638             } else {
1639                 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795.
1640                 compactName = ci.name;
1641             }
1642         }
1643 
1644         if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1645             // If we're still null/empty here, then check if we have a presentation
1646             // string that takes precedence that we could return, otherwise display
1647             // "unknown" string.
1648             if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
1649                 compactName = context.getString(R.string.private_num);
1650             } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) {
1651                 compactName = context.getString(R.string.payphone);
1652             } else {
1653                 compactName = context.getString(R.string.unknown);
1654             }
1655         }
1656         if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
1657         return compactName;
1658     }
1659 
1660     /**
1661      * Returns true if the specified Call is a "conference call", meaning
1662      * that it owns more than one Connection object.  This information is
1663      * used to trigger certain UI changes that appear when a conference
1664      * call is active (like displaying the label "Conference call", and
1665      * enabling the "Manage conference" UI.)
1666      *
1667      * Watch out: This method simply checks the number of Connections,
1668      * *not* their states.  So if a Call has (for example) one ACTIVE
1669      * connection and one DISCONNECTED connection, this method will return
1670      * true (which is unintuitive, since the Call isn't *really* a
1671      * conference call any more.)
1672      *
1673      * @return true if the specified call has more than one connection (in any state.)
1674      */
isConferenceCall(Call call)1675     static boolean isConferenceCall(Call call) {
1676         // CDMA phones don't have the same concept of "conference call" as
1677         // GSM phones do; there's no special "conference call" state of
1678         // the UI or a "manage conference" function.  (Instead, when
1679         // you're in a 3-way call, all we can do is display the "generic"
1680         // state of the UI.)  So as far as the in-call UI is concerned,
1681         // Conference corresponds to generic display.
1682         final PhoneGlobals app = PhoneGlobals.getInstance();
1683         int phoneType = call.getPhone().getPhoneType();
1684         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1685             CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
1686             if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL)
1687                     || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1688                     && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) {
1689                 return true;
1690             }
1691         } else {
1692             List<Connection> connections = call.getConnections();
1693             if (connections != null && connections.size() > 1) {
1694                 return true;
1695             }
1696         }
1697         return false;
1698 
1699         // TODO: We may still want to change the semantics of this method
1700         // to say that a given call is only really a conference call if
1701         // the number of ACTIVE connections, not the total number of
1702         // connections, is greater than one.  (See warning comment in the
1703         // javadoc above.)
1704         // Here's an implementation of that:
1705         //        if (connections == null) {
1706         //            return false;
1707         //        }
1708         //        int numActiveConnections = 0;
1709         //        for (Connection conn : connections) {
1710         //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1711         //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1712         //            if (numActiveConnections > 1) {
1713         //                return true;
1714         //            }
1715         //        }
1716         //        return false;
1717     }
1718 
1719     /**
1720      * Launch the Dialer to start a new call.
1721      * This is just a wrapper around the ACTION_DIAL intent.
1722      */
startNewCall(final CallManager cm)1723     /* package */ static boolean startNewCall(final CallManager cm) {
1724         final PhoneGlobals app = PhoneGlobals.getInstance();
1725 
1726         // Sanity-check that this is OK given the current state of the phone.
1727         if (!okToAddCall(cm)) {
1728             Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1729             dumpCallManager();
1730             return false;
1731         }
1732 
1733         Intent intent = new Intent(Intent.ACTION_DIAL);
1734         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1735 
1736         // when we request the dialer come up, we also want to inform
1737         // it that we're going through the "add call" option from the
1738         // InCallScreen / PhoneUtils.
1739         intent.putExtra(ADD_CALL_MODE_KEY, true);
1740         try {
1741             app.startActivity(intent);
1742         } catch (ActivityNotFoundException e) {
1743             // This is rather rare but possible.
1744             // Note: this method is used even when the phone is encrypted. At that moment
1745             // the system may not find any Activity which can accept this Intent.
1746             Log.e(LOG_TAG, "Activity for adding calls isn't found.");
1747             return false;
1748         }
1749 
1750         return true;
1751     }
1752 
1753     /**
1754      * Turns on/off speaker.
1755      *
1756      * @param context Context
1757      * @param flag True when speaker should be on. False otherwise.
1758      * @param store True when the settings should be stored in the device.
1759      */
turnOnSpeaker(Context context, boolean flag, boolean store)1760     /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) {
1761         if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
1762         final PhoneGlobals app = PhoneGlobals.getInstance();
1763 
1764         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1765         audioManager.setSpeakerphoneOn(flag);
1766 
1767         // record the speaker-enable value
1768         if (store) {
1769             sIsSpeakerEnabled = flag;
1770         }
1771 
1772         // We also need to make a fresh call to PhoneApp.updateWakeState()
1773         // any time the speaker state changes, since the screen timeout is
1774         // sometimes different depending on whether or not the speaker is
1775         // in use.
1776         app.updateWakeState();
1777 
1778         app.mCM.setEchoSuppressionEnabled();
1779     }
1780 
1781     /**
1782      * Restore the speaker mode, called after a wired headset disconnect
1783      * event.
1784      */
restoreSpeakerMode(Context context)1785     static void restoreSpeakerMode(Context context) {
1786         if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1787 
1788         // change the mode if needed.
1789         if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1790             turnOnSpeaker(context, sIsSpeakerEnabled, false);
1791         }
1792     }
1793 
isSpeakerOn(Context context)1794     static boolean isSpeakerOn(Context context) {
1795         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1796         return audioManager.isSpeakerphoneOn();
1797     }
1798 
1799 
turnOnNoiseSuppression(Context context, boolean flag, boolean store)1800     static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) {
1801         if (DBG) log("turnOnNoiseSuppression: " + flag);
1802         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1803 
1804         PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1805         if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1806             return;
1807         }
1808 
1809         if (flag) {
1810             audioManager.setParameters("noise_suppression=auto");
1811         } else {
1812             audioManager.setParameters("noise_suppression=off");
1813         }
1814 
1815         // record the speaker-enable value
1816         if (store) {
1817             sIsNoiseSuppressionEnabled = flag;
1818         }
1819 
1820         // TODO: implement and manage ICON
1821 
1822     }
1823 
restoreNoiseSuppression(Context context)1824     static void restoreNoiseSuppression(Context context) {
1825         if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled);
1826 
1827         PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1828         if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1829             return;
1830         }
1831 
1832         // change the mode if needed.
1833         if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) {
1834             turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false);
1835         }
1836     }
1837 
isNoiseSuppressionOn(Context context)1838     static boolean isNoiseSuppressionOn(Context context) {
1839 
1840         PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1841         if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1842             return false;
1843         }
1844 
1845         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1846         String noiseSuppression = audioManager.getParameters("noise_suppression");
1847         if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression);
1848         if (noiseSuppression.contains("off")) {
1849             return false;
1850         } else {
1851             return true;
1852         }
1853     }
1854 
isInEmergencyCall(CallManager cm)1855     static boolean isInEmergencyCall(CallManager cm) {
1856         Call fgCall = cm.getActiveFgCall();
1857         // isIdle includes checks for the DISCONNECTING/DISCONNECTED state.
1858         if(!fgCall.isIdle()) {
1859             for (Connection cn : fgCall.getConnections()) {
1860                 if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
1861                         cn.getAddress())) {
1862                     return true;
1863                 }
1864             }
1865         }
1866         return false;
1867     }
1868 
1869     /**
1870      * Get the mute state of foreground phone, which has the current
1871      * foreground call
1872      */
getMute()1873     static boolean getMute() {
1874         return false;
1875     }
1876 
setAudioMode()1877     /* package */ static void setAudioMode() {
1878     }
1879 
1880     /**
1881      * Sets the audio mode per current phone state.
1882      */
setAudioMode(CallManager cm)1883     /* package */ static void setAudioMode(CallManager cm) {
1884     }
1885 
1886     /**
1887      * Look for ANY connections on the phone that qualify as being
1888      * disconnected.
1889      *
1890      * @return true if we find a connection that is disconnected over
1891      * all the phone's call objects.
1892      */
hasDisconnectedConnections(Phone phone)1893     /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1894         return hasDisconnectedConnections(phone.getForegroundCall()) ||
1895                 hasDisconnectedConnections(phone.getBackgroundCall()) ||
1896                 hasDisconnectedConnections(phone.getRingingCall());
1897     }
1898 
1899     /**
1900      * Iterate over all connections in a call to see if there are any
1901      * that are not alive (disconnected or idle).
1902      *
1903      * @return true if we find a connection that is disconnected, and
1904      * pending removal via
1905      * {@link com.android.internal.telephony.Call#clearDisconnected()}.
1906      */
hasDisconnectedConnections(Call call)1907     private static final boolean hasDisconnectedConnections(Call call) {
1908         // look through all connections for non-active ones.
1909         for (Connection c : call.getConnections()) {
1910             if (!c.isAlive()) {
1911                 return true;
1912             }
1913         }
1914         return false;
1915     }
1916 
1917     //
1918     // Misc UI policy helper functions
1919     //
1920 
1921     /**
1922      * @return true if we're allowed to hold calls, given the current
1923      * state of the Phone.
1924      */
okToHoldCall(CallManager cm)1925     /* package */ static boolean okToHoldCall(CallManager cm) {
1926         final Call fgCall = cm.getActiveFgCall();
1927         final boolean hasHoldingCall = cm.hasActiveBgCall();
1928         final Call.State fgCallState = fgCall.getState();
1929 
1930         // The "Hold" control is disabled entirely if there's
1931         // no way to either hold or unhold in the current state.
1932         final boolean okToHold = (fgCallState == Call.State.ACTIVE) && !hasHoldingCall;
1933         final boolean okToUnhold = cm.hasActiveBgCall() && (fgCallState == Call.State.IDLE);
1934         final boolean canHold = okToHold || okToUnhold;
1935 
1936         return canHold;
1937     }
1938 
1939     /**
1940      * @return true if we support holding calls, given the current
1941      * state of the Phone.
1942      */
okToSupportHold(CallManager cm)1943     /* package */ static boolean okToSupportHold(CallManager cm) {
1944         boolean supportsHold = false;
1945 
1946         final Call fgCall = cm.getActiveFgCall();
1947         final boolean hasHoldingCall = cm.hasActiveBgCall();
1948         final Call.State fgCallState = fgCall.getState();
1949 
1950         if (TelephonyCapabilities.supportsHoldAndUnhold(fgCall.getPhone())) {
1951             // This phone has the concept of explicit "Hold" and "Unhold" actions.
1952             supportsHold = true;
1953         } else if (hasHoldingCall && (fgCallState == Call.State.IDLE)) {
1954             // Even when foreground phone device doesn't support hold/unhold, phone devices
1955             // for background holding calls may do.
1956             final Call bgCall = cm.getFirstActiveBgCall();
1957             if (bgCall != null &&
1958                     TelephonyCapabilities.supportsHoldAndUnhold(bgCall.getPhone())) {
1959                 supportsHold = true;
1960             }
1961         }
1962         return supportsHold;
1963     }
1964 
1965     /**
1966      * @return true if we're allowed to swap calls, given the current
1967      * state of the Phone.
1968      */
okToSwapCalls(CallManager cm)1969     /* package */ static boolean okToSwapCalls(CallManager cm) {
1970         int phoneType = cm.getDefaultPhone().getPhoneType();
1971         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1972             // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
1973             // state by either accepting a Call Waiting or by merging two calls
1974             PhoneGlobals app = PhoneGlobals.getInstance();
1975             return (app.cdmaPhoneCallState.getCurrentCallState()
1976                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
1977         } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1978                 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
1979                 || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
1980                 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
1981             // GSM: "Swap" is available if both lines are in use and there's no
1982             // incoming call.  (Actually we need to verify that the active
1983             // call really is in the ACTIVE state and the holding call really
1984             // is in the HOLDING state, since you *can't* actually swap calls
1985             // when the foreground call is DIALING or ALERTING.)
1986             return !cm.hasActiveRingingCall()
1987                     && (cm.getActiveFgCall().getState() == Call.State.ACTIVE)
1988                     && (cm.getFirstActiveBgCall().getState() == Call.State.HOLDING);
1989         } else {
1990             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1991         }
1992     }
1993 
1994     /**
1995      * @return true if we're allowed to merge calls, given the current
1996      * state of the Phone.
1997      */
okToMergeCalls(CallManager cm)1998     /* package */ static boolean okToMergeCalls(CallManager cm) {
1999         int phoneType = cm.getFgPhone().getPhoneType();
2000         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2001             // CDMA: "Merge" is enabled only when the user is in a 3Way call.
2002             PhoneGlobals app = PhoneGlobals.getInstance();
2003             return ((app.cdmaPhoneCallState.getCurrentCallState()
2004                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
2005                     && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
2006         } else {
2007             // GSM: "Merge" is available if both lines are in use and there's no
2008             // incoming call, *and* the current conference isn't already
2009             // "full".
2010             // TODO: shall move all okToMerge logic to CallManager
2011             return !cm.hasActiveRingingCall() && cm.hasActiveFgCall()
2012                     && cm.hasActiveBgCall()
2013                     && cm.canConference(cm.getFirstActiveBgCall());
2014         }
2015     }
2016 
2017     /**
2018      * @return true if the UI should let you add a new call, given the current
2019      * state of the Phone.
2020      */
okToAddCall(CallManager cm)2021     /* package */ static boolean okToAddCall(CallManager cm) {
2022         Phone phone = cm.getActiveFgCall().getPhone();
2023 
2024         // "Add call" is never allowed in emergency callback mode (ECM).
2025         if (isPhoneInEcm(phone)) {
2026             return false;
2027         }
2028 
2029         int phoneType = phone.getPhoneType();
2030         final Call.State fgCallState = cm.getActiveFgCall().getState();
2031         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2032            // CDMA: "Add call" button is only enabled when:
2033            // - ForegroundCall is in ACTIVE state
2034            // - After 30 seconds of user Ignoring/Missing a Call Waiting call.
2035             PhoneGlobals app = PhoneGlobals.getInstance();
2036             return ((fgCallState == Call.State.ACTIVE)
2037                     && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
2038         } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
2039                 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
2040                 || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
2041                 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
2042             // GSM: "Add call" is available only if ALL of the following are true:
2043             // - There's no incoming ringing call
2044             // - There's < 2 lines in use
2045             // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
2046             //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
2047             final boolean hasRingingCall = cm.hasActiveRingingCall();
2048             final boolean hasActiveCall = cm.hasActiveFgCall();
2049             final boolean hasHoldingCall = cm.hasActiveBgCall();
2050             final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2051 
2052             return !hasRingingCall
2053                     && !allLinesTaken
2054                     && ((fgCallState == Call.State.ACTIVE)
2055                         || (fgCallState == Call.State.IDLE)
2056                         || (fgCallState == Call.State.DISCONNECTED));
2057         } else {
2058             throw new IllegalStateException("Unexpected phone type: " + phoneType);
2059         }
2060     }
2061 
2062     /**
2063      * Based on the input CNAP number string,
2064      * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
2065      * Otherwise, return CNAP_SPECIAL_CASE_NO.
2066      */
checkCnapSpecialCases(String n)2067     private static int checkCnapSpecialCases(String n) {
2068         if (n.equals("PRIVATE") ||
2069                 n.equals("P") ||
2070                 n.equals("RES")) {
2071             if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
2072             return PhoneConstants.PRESENTATION_RESTRICTED;
2073         } else if (n.equals("UNAVAILABLE") ||
2074                 n.equals("UNKNOWN") ||
2075                 n.equals("UNA") ||
2076                 n.equals("U")) {
2077             if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
2078             return PhoneConstants.PRESENTATION_UNKNOWN;
2079         } else {
2080             if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
2081             return CNAP_SPECIAL_CASE_NO;
2082         }
2083     }
2084 
2085     /**
2086      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
2087      * from the network to indicate different number presentations, convert them to
2088      * expected number and presentation values within the CallerInfo object.
2089      * @param number number we use to verify if we are in a corner case
2090      * @param presentation presentation value used to verify if we are in a corner case
2091      * @return the new String that should be used for the phone number
2092      */
modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)2093     /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
2094             String number, int presentation) {
2095         // Obviously we return number if ci == null, but still return number if
2096         // number == null, because in these cases the correct string will still be
2097         // displayed/logged after this function returns based on the presentation value.
2098         if (ci == null || number == null) return number;
2099 
2100         if (DBG) {
2101             log("modifyForSpecialCnapCases: initially, number="
2102                     + toLogSafePhoneNumber(number)
2103                     + ", presentation=" + presentation + " ci " + ci);
2104         }
2105 
2106         // "ABSENT NUMBER" is a possible value we could get from the network as the
2107         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
2108         // and fix the presentation to be the same.
2109         final String[] absentNumberValues =
2110                 context.getResources().getStringArray(R.array.absent_num);
2111         if (Arrays.asList(absentNumberValues).contains(number)
2112                 && presentation == PhoneConstants.PRESENTATION_ALLOWED) {
2113             number = context.getString(R.string.unknown);
2114             ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
2115         }
2116 
2117         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
2118         // cases only apply if we received an allowed presentation from the network, so check
2119         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
2120         // match the presentation passed in for verification (meaning we changed it previously
2121         // because it's a corner case and we're being called from a different entry point).
2122         if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED
2123                 || (ci.numberPresentation != presentation
2124                         && presentation == PhoneConstants.PRESENTATION_ALLOWED)) {
2125             int cnapSpecialCase = checkCnapSpecialCases(number);
2126             if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
2127                 // For all special strings, change number & numberPresentation.
2128                 if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) {
2129                     number = context.getString(R.string.private_num);
2130                 } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) {
2131                     number = context.getString(R.string.unknown);
2132                 }
2133                 if (DBG) {
2134                     log("SpecialCnap: number=" + toLogSafePhoneNumber(number)
2135                             + "; presentation now=" + cnapSpecialCase);
2136                 }
2137                 ci.numberPresentation = cnapSpecialCase;
2138             }
2139         }
2140         if (DBG) {
2141             log("modifyForSpecialCnapCases: returning number string="
2142                     + toLogSafePhoneNumber(number));
2143         }
2144         return number;
2145     }
2146 
2147     //
2148     // Support for 3rd party phone service providers.
2149     //
2150 
2151     /**
2152      * Check if a phone number can be route through a 3rd party
2153      * gateway. The number must be a global phone number in numerical
2154      * form (1-800-666-SEXY won't work).
2155      *
2156      * MMI codes and the like cannot be used as a dial number for the
2157      * gateway either.
2158      *
2159      * @param number To be dialed via a 3rd party gateway.
2160      * @return true If the number can be routed through the 3rd party network.
2161      */
isRoutableViaGateway(String number)2162     private static boolean isRoutableViaGateway(String number) {
2163         if (TextUtils.isEmpty(number)) {
2164             return false;
2165         }
2166         number = PhoneNumberUtils.stripSeparators(number);
2167         if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) {
2168             return false;
2169         }
2170         number = PhoneNumberUtils.extractNetworkPortion(number);
2171         return PhoneNumberUtils.isGlobalPhoneNumber(number);
2172     }
2173 
2174     /**
2175      * Returns whether the phone is in ECM ("Emergency Callback Mode") or not.
2176      */
isPhoneInEcm(Phone phone)2177     /* package */ static boolean isPhoneInEcm(Phone phone) {
2178         if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) {
2179             return phone.isInEcm();
2180         }
2181         return false;
2182     }
2183 
2184     /**
2185      * Returns the most appropriate Phone object to handle a call
2186      * to the specified number.
2187      *
2188      * @param cm the CallManager.
2189      * @param scheme the scheme from the data URI that the number originally came from.
2190      * @param number the phone number, or SIP address.
2191      */
pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, String primarySipUri, ComponentName thirdPartyCallComponent)2192     public static Phone pickPhoneBasedOnNumber(CallManager cm, String scheme, String number,
2193             String primarySipUri, ComponentName thirdPartyCallComponent) {
2194         if (DBG) {
2195             log("pickPhoneBasedOnNumber: scheme " + scheme
2196                     + ", number " + toLogSafePhoneNumber(number)
2197                     + ", sipUri "
2198                     + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null")
2199                     + ", thirdPartyCallComponent: " + thirdPartyCallComponent);
2200         }
2201 
2202         if (primarySipUri != null) {
2203             Phone phone = getSipPhoneFromUri(cm, primarySipUri);
2204             if (phone != null) return phone;
2205         }
2206 
2207         return cm.getDefaultPhone();
2208     }
2209 
getSipPhoneFromUri(CallManager cm, String target)2210     public static Phone getSipPhoneFromUri(CallManager cm, String target) {
2211         for (Phone phone : cm.getAllPhones()) {
2212             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
2213                 String sipUri = ((SipPhone) phone).getSipUri();
2214                 if (target.equals(sipUri)) {
2215                     if (DBG) log("- pickPhoneBasedOnNumber:" +
2216                             "found SipPhone! obj = " + phone + ", "
2217                             + phone.getClass());
2218                     return phone;
2219                 }
2220             }
2221         }
2222         return null;
2223     }
2224 
2225     /**
2226      * Returns true when the given call is in INCOMING state and there's no foreground phone call,
2227      * meaning the call is the first real incoming call the phone is having.
2228      */
isRealIncomingCall(Call.State state)2229     public static boolean isRealIncomingCall(Call.State state) {
2230         return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
2231     }
2232 
getPresentationString(Context context, int presentation)2233     public static String getPresentationString(Context context, int presentation) {
2234         String name = context.getString(R.string.unknown);
2235         if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
2236             name = context.getString(R.string.private_num);
2237         } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
2238             name = context.getString(R.string.payphone);
2239         }
2240         return name;
2241     }
2242 
sendViewNotificationAsync(Context context, Uri contactUri)2243     public static void sendViewNotificationAsync(Context context, Uri contactUri) {
2244         if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")");
2245         Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri);
2246         intent.setClassName("com.android.contacts",
2247                 "com.android.contacts.ViewNotificationService");
2248         context.startService(intent);
2249     }
2250 
2251     //
2252     // General phone and call state debugging/testing code
2253     //
2254 
dumpCallState(Phone phone)2255     /* package */ static void dumpCallState(Phone phone) {
2256         PhoneGlobals app = PhoneGlobals.getInstance();
2257         Log.d(LOG_TAG, "dumpCallState():");
2258         Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
2259               + ", state = " + phone.getState());
2260 
2261         StringBuilder b = new StringBuilder(128);
2262 
2263         Call call = phone.getForegroundCall();
2264         b.setLength(0);
2265         b.append("  - FG call: ").append(call.getState());
2266         b.append(" isAlive ").append(call.getState().isAlive());
2267         b.append(" isRinging ").append(call.getState().isRinging());
2268         b.append(" isDialing ").append(call.getState().isDialing());
2269         b.append(" isIdle ").append(call.isIdle());
2270         b.append(" hasConnections ").append(call.hasConnections());
2271         Log.d(LOG_TAG, b.toString());
2272 
2273         call = phone.getBackgroundCall();
2274         b.setLength(0);
2275         b.append("  - BG call: ").append(call.getState());
2276         b.append(" isAlive ").append(call.getState().isAlive());
2277         b.append(" isRinging ").append(call.getState().isRinging());
2278         b.append(" isDialing ").append(call.getState().isDialing());
2279         b.append(" isIdle ").append(call.isIdle());
2280         b.append(" hasConnections ").append(call.hasConnections());
2281         Log.d(LOG_TAG, b.toString());
2282 
2283         call = phone.getRingingCall();
2284         b.setLength(0);
2285         b.append("  - RINGING call: ").append(call.getState());
2286         b.append(" isAlive ").append(call.getState().isAlive());
2287         b.append(" isRinging ").append(call.getState().isRinging());
2288         b.append(" isDialing ").append(call.getState().isDialing());
2289         b.append(" isIdle ").append(call.isIdle());
2290         b.append(" hasConnections ").append(call.hasConnections());
2291         Log.d(LOG_TAG, b.toString());
2292 
2293 
2294         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
2295         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
2296         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
2297         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2298         b.setLength(0);
2299         b.append("  - hasRingingCall ").append(hasRingingCall);
2300         b.append(" hasActiveCall ").append(hasActiveCall);
2301         b.append(" hasHoldingCall ").append(hasHoldingCall);
2302         b.append(" allLinesTaken ").append(allLinesTaken);
2303         Log.d(LOG_TAG, b.toString());
2304 
2305         // On CDMA phones, dump out the CdmaPhoneCallState too:
2306         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
2307             if (app.cdmaPhoneCallState != null) {
2308                 Log.d(LOG_TAG, "  - CDMA call state: "
2309                       + app.cdmaPhoneCallState.getCurrentCallState());
2310             } else {
2311                 Log.d(LOG_TAG, "  - CDMA device, but null cdmaPhoneCallState!");
2312             }
2313         }
2314     }
2315 
log(String msg)2316     private static void log(String msg) {
2317         Log.d(LOG_TAG, msg);
2318     }
2319 
dumpCallManager()2320     static void dumpCallManager() {
2321         Call call;
2322         CallManager cm = PhoneGlobals.getInstance().mCM;
2323         StringBuilder b = new StringBuilder(128);
2324 
2325 
2326 
2327         Log.d(LOG_TAG, "############### dumpCallManager() ##############");
2328         // TODO: Don't log "cm" itself, since CallManager.toString()
2329         // already spews out almost all this same information.
2330         // We should fix CallManager.toString() to be more minimal, and
2331         // use an explicit dumpState() method for the verbose dump.
2332         // Log.d(LOG_TAG, "CallManager: " + cm
2333         //         + ", state = " + cm.getState());
2334         Log.d(LOG_TAG, "CallManager: state = " + cm.getState());
2335         b.setLength(0);
2336         call = cm.getActiveFgCall();
2337         b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO ");
2338         b.append(call);
2339         b.append( "  State: ").append(cm.getActiveFgCallState());
2340         b.append( "  Conn: ").append(cm.getFgCallConnections());
2341         Log.d(LOG_TAG, b.toString());
2342         b.setLength(0);
2343         call = cm.getFirstActiveBgCall();
2344         b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO ");
2345         b.append(call);
2346         b.append( "  State: ").append(cm.getFirstActiveBgCall().getState());
2347         b.append( "  Conn: ").append(cm.getBgCallConnections());
2348         Log.d(LOG_TAG, b.toString());
2349         b.setLength(0);
2350         call = cm.getFirstActiveRingingCall();
2351         b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO ");
2352         b.append(call);
2353         b.append( "  State: ").append(cm.getFirstActiveRingingCall().getState());
2354         Log.d(LOG_TAG, b.toString());
2355 
2356 
2357 
2358         for (Phone phone : CallManager.getInstance().getAllPhones()) {
2359             if (phone != null) {
2360                 Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName()
2361                         + ", state = " + phone.getState());
2362                 b.setLength(0);
2363                 call = phone.getForegroundCall();
2364                 b.append(" - FG call: ").append(call);
2365                 b.append( "  State: ").append(call.getState());
2366                 b.append( "  Conn: ").append(call.hasConnections());
2367                 Log.d(LOG_TAG, b.toString());
2368                 b.setLength(0);
2369                 call = phone.getBackgroundCall();
2370                 b.append(" - BG call: ").append(call);
2371                 b.append( "  State: ").append(call.getState());
2372                 b.append( "  Conn: ").append(call.hasConnections());
2373                 Log.d(LOG_TAG, b.toString());b.setLength(0);
2374                 call = phone.getRingingCall();
2375                 b.append(" - RINGING call: ").append(call);
2376                 b.append( "  State: ").append(call.getState());
2377                 b.append( "  Conn: ").append(call.hasConnections());
2378                 Log.d(LOG_TAG, b.toString());
2379             }
2380         }
2381 
2382         Log.d(LOG_TAG, "############## END dumpCallManager() ###############");
2383     }
2384 
2385     /**
2386      * @return if the context is in landscape orientation.
2387      */
isLandscape(Context context)2388     public static boolean isLandscape(Context context) {
2389         return context.getResources().getConfiguration().orientation
2390                 == Configuration.ORIENTATION_LANDSCAPE;
2391     }
2392 
makePstnPhoneAccountHandle(String id)2393     public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) {
2394         return makePstnPhoneAccountHandleWithPrefix(id, "", false);
2395     }
2396 
makePstnPhoneAccountHandle(int phoneId)2397     public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
2398         return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
2399     }
2400 
makePstnPhoneAccountHandle(Phone phone)2401     public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
2402         return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
2403     }
2404 
makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)2405     public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
2406             Phone phone, String prefix, boolean isEmergency) {
2407         // TODO: Should use some sort of special hidden flag to decorate this account as
2408         // an emergency-only account
2409         String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix +
2410                 String.valueOf(phone.getFullIccSerialNumber());
2411         return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency);
2412     }
2413 
makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)2414     public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
2415             String id, String prefix, boolean isEmergency) {
2416         ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
2417         return new PhoneAccountHandle(pstnConnectionServiceName, id);
2418     }
2419 
getSubIdForPhoneAccount(PhoneAccount phoneAccount)2420     public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
2421         if (phoneAccount != null
2422                 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
2423             return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle());
2424         }
2425         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
2426     }
2427 
getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)2428     public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) {
2429         Phone phone = getPhoneForPhoneAccountHandle(handle);
2430         if (phone != null) {
2431             return phone.getSubId();
2432         }
2433         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
2434     }
2435 
getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)2436     public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) {
2437         if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
2438             return getPhoneFromIccId(handle.getId());
2439         }
2440         return null;
2441     }
2442 
2443 
2444     /**
2445      * Determine if a given phone account corresponds to an active SIM
2446      *
2447      * @param sm An instance of the subscription manager so it is not recreated for each calling of
2448      * this method.
2449      * @param handle The handle for the phone account to check
2450      * @return {@code true} If there is an active SIM for this phone account,
2451      * {@code false} otherwise.
2452      */
isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)2453     public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) {
2454         return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null;
2455     }
2456 
getPstnConnectionServiceName()2457     private static ComponentName getPstnConnectionServiceName() {
2458         return PSTN_CONNECTION_SERVICE_COMPONENT;
2459     }
2460 
getPhoneFromIccId(String iccId)2461     private static Phone getPhoneFromIccId(String iccId) {
2462         if (!TextUtils.isEmpty(iccId)) {
2463             for (Phone phone : PhoneFactory.getPhones()) {
2464                 String phoneIccId = phone.getFullIccSerialNumber();
2465                 if (iccId.equals(phoneIccId)) {
2466                     return phone;
2467                 }
2468             }
2469         }
2470         return null;
2471     }
2472 
2473     /**
2474      * Register ICC status for all phones.
2475      */
registerIccStatus(Handler handler, int event)2476     static final void registerIccStatus(Handler handler, int event) {
2477         for (Phone phone : PhoneFactory.getPhones()) {
2478             IccCard sim = phone.getIccCard();
2479             if (sim != null) {
2480                 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId());
2481                 sim.registerForNetworkLocked(handler, event, phone);
2482             }
2483         }
2484     }
2485 
2486     /**
2487      * Set the radio power on/off state for all phones.
2488      *
2489      * @param enabled true means on, false means off.
2490      */
setRadioPower(boolean enabled)2491     static final void setRadioPower(boolean enabled) {
2492         for (Phone phone : PhoneFactory.getPhones()) {
2493             phone.setRadioPower(enabled);
2494         }
2495     }
2496 }
2497