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