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