• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.ProgressDialog;
22 import android.bluetooth.IBluetoothHeadsetPhone;
23 import android.content.ActivityNotFoundException;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.res.Configuration;
29 import android.media.AudioManager;
30 import android.net.Uri;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.os.SystemProperties;
35 import android.telecom.PhoneAccount;
36 import android.telecom.VideoProfile;
37 import android.telephony.PhoneNumberUtils;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.KeyEvent;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.WindowManager;
44 import android.widget.EditText;
45 import android.widget.Toast;
46 
47 import com.android.internal.telephony.Call;
48 import com.android.internal.telephony.CallManager;
49 import com.android.internal.telephony.CallStateException;
50 import com.android.internal.telephony.CallerInfo;
51 import com.android.internal.telephony.CallerInfoAsyncQuery;
52 import com.android.internal.telephony.Connection;
53 import com.android.internal.telephony.MmiCode;
54 import com.android.internal.telephony.Phone;
55 import com.android.internal.telephony.PhoneConstants;
56 import com.android.internal.telephony.TelephonyCapabilities;
57 import com.android.internal.telephony.TelephonyProperties;
58 import com.android.internal.telephony.sip.SipPhone;
59 import com.android.phone.CallGatewayManager.RawGatewayInfo;
60 
61 import java.util.Arrays;
62 import java.util.List;
63 
64 /**
65  * Misc utilities for the Phone app.
66  */
67 public class PhoneUtils {
68     private static final String LOG_TAG = "PhoneUtils";
69     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
70 
71     // Do not check in with VDBG = true, since that may write PII to the system log.
72     private static final boolean VDBG = false;
73 
74     /** Control stack trace for Audio Mode settings */
75     private static final boolean DBG_SETAUDIOMODE_STACK = false;
76 
77     /** Identifier for the "Add Call" intent extra. */
78     static final String ADD_CALL_MODE_KEY = "add_call_mode";
79 
80     // Return codes from placeCall()
81     static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
82     static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
83     static final int CALL_STATUS_FAILED = 2;  // The call failed
84 
85     // State of the Phone's audio modes
86     // Each state can move to the other states, but within the state only certain
87     //  transitions for AudioManager.setMode() are allowed.
88     static final int AUDIO_IDLE = 0;  /** audio behaviour at phone idle */
89     static final int AUDIO_RINGING = 1;  /** audio behaviour while ringing */
90     static final int AUDIO_OFFHOOK = 2;  /** audio behaviour while in call. */
91 
92     // USSD string length for MMI operations
93     static final int MIN_USSD_LEN = 1;
94     static final int MAX_USSD_LEN = 160;
95 
96     /** Speaker state, persisting between wired headset connection events */
97     private static boolean sIsSpeakerEnabled = false;
98 
99     /** Static handler for the connection/mute tracking */
100     private static ConnectionHandler mConnectionHandler;
101 
102     /** Phone state changed event*/
103     private static final int PHONE_STATE_CHANGED = -1;
104 
105     /** check status then decide whether answerCall */
106     private static final int MSG_CHECK_STATUS_ANSWERCALL = 100;
107 
108     /** poll phone DISCONNECTING status interval */
109     private static final int DISCONNECTING_POLLING_INTERVAL_MS = 200;
110 
111     /** poll phone DISCONNECTING status times limit */
112     private static final int DISCONNECTING_POLLING_TIMES_LIMIT = 8;
113 
114     /** Define for not a special CNAP string */
115     private static final int CNAP_SPECIAL_CASE_NO = -1;
116 
117     /** Noise suppression status as selected by user */
118     private static boolean sIsNoiseSuppressionEnabled = true;
119 
120     private static class FgRingCalls {
121         private Call fgCall;
122         private Call ringing;
FgRingCalls(Call fg, Call ring)123         public FgRingCalls(Call fg, Call ring) {
124             fgCall = fg;
125             ringing = ring;
126         }
127     }
128 
129     /** USSD information used to aggregate all USSD messages */
130     private static AlertDialog sUssdDialog = null;
131     private static StringBuilder sUssdMsg = new StringBuilder();
132 
133     /**
134      * Handler that tracks the connections and updates the value of the
135      * Mute settings for each connection as needed.
136      */
137     private static class ConnectionHandler extends Handler {
138         @Override
handleMessage(Message msg)139         public void handleMessage(Message msg) {
140             switch (msg.what) {
141                 case MSG_CHECK_STATUS_ANSWERCALL:
142                     FgRingCalls frC = (FgRingCalls) msg.obj;
143                     // wait for finishing disconnecting
144                     // before check the ringing call state
145                     if ((frC.fgCall != null) &&
146                         (frC.fgCall.getState() == Call.State.DISCONNECTING) &&
147                         (msg.arg1 < DISCONNECTING_POLLING_TIMES_LIMIT)) {
148                         Message retryMsg =
149                             mConnectionHandler.obtainMessage(MSG_CHECK_STATUS_ANSWERCALL);
150                         retryMsg.arg1 = 1 + msg.arg1;
151                         retryMsg.obj = msg.obj;
152                         mConnectionHandler.sendMessageDelayed(retryMsg,
153                             DISCONNECTING_POLLING_INTERVAL_MS);
154                     // since hangupActiveCall() also accepts the ringing call
155                     // check if the ringing call was already answered or not
156                     // only answer it when the call still is ringing
157                     } else if (frC.ringing.isRinging()) {
158                         if (msg.arg1 == DISCONNECTING_POLLING_TIMES_LIMIT) {
159                             Log.e(LOG_TAG, "DISCONNECTING time out");
160                         }
161                         answerCall(frC.ringing);
162                     }
163                     break;
164             }
165         }
166     }
167 
168     /**
169      * Register the ConnectionHandler with the phone, to receive connection events
170      */
initializeConnectionHandler(CallManager cm)171     public static void initializeConnectionHandler(CallManager cm) {
172         if (mConnectionHandler == null) {
173             mConnectionHandler = new ConnectionHandler();
174         }
175 
176         // pass over cm as user.obj
177         cm.registerForPreciseCallStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, cm);
178 
179     }
180 
181     /** This class is never instantiated. */
PhoneUtils()182     private PhoneUtils() {
183     }
184 
185     /**
186      * Answer the currently-ringing call.
187      *
188      * @return true if we answered the call, or false if there wasn't
189      *         actually a ringing incoming call, or some other error occurred.
190      *
191      * @see #answerAndEndHolding(CallManager, Call)
192      * @see #answerAndEndActive(CallManager, Call)
193      */
answerCall(Call ringingCall)194     /* package */ static boolean answerCall(Call ringingCall) {
195         log("answerCall(" + ringingCall + ")...");
196         final PhoneGlobals app = PhoneGlobals.getInstance();
197         final CallNotifier notifier = app.notifier;
198 
199         final Phone phone = ringingCall.getPhone();
200         final boolean phoneIsCdma = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
201         boolean answered = false;
202         IBluetoothHeadsetPhone btPhone = null;
203 
204         if (phoneIsCdma) {
205             // Stop any signalInfo tone being played when a Call waiting gets answered
206             if (ringingCall.getState() == Call.State.WAITING) {
207                 notifier.stopSignalInfoTone();
208             }
209         }
210 
211         if (ringingCall != null && ringingCall.isRinging()) {
212             if (DBG) log("answerCall: call state = " + ringingCall.getState());
213             try {
214                 if (phoneIsCdma) {
215                     if (app.cdmaPhoneCallState.getCurrentCallState()
216                             == CdmaPhoneCallState.PhoneCallState.IDLE) {
217                         // This is the FIRST incoming call being answered.
218                         // Set the Phone Call State to SINGLE_ACTIVE
219                         app.cdmaPhoneCallState.setCurrentCallState(
220                                 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
221                     } else {
222                         // This is the CALL WAITING call being answered.
223                         // Set the Phone Call State to CONF_CALL
224                         app.cdmaPhoneCallState.setCurrentCallState(
225                                 CdmaPhoneCallState.PhoneCallState.CONF_CALL);
226                         // Enable "Add Call" option after answering a Call Waiting as the user
227                         // should be allowed to add another call in case one of the parties
228                         // drops off
229                         app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
230                     }
231                 }
232 
233                 final boolean isRealIncomingCall = isRealIncomingCall(ringingCall.getState());
234 
235                 //if (DBG) log("sPhone.acceptCall");
236                 app.mCM.acceptCall(ringingCall);
237                 answered = true;
238 
239                 setAudioMode();
240 
241                 // Check is phone in any dock, and turn on speaker accordingly
242                 final boolean speakerActivated = activateSpeakerIfDocked(phone);
243 
244                 final BluetoothManager btManager = app.getBluetoothManager();
245 
246                 // When answering a phone call, the user will move the phone near to her/his ear
247                 // and start conversation, without checking its speaker status. If some other
248                 // application turned on the speaker mode before the call and didn't turn it off,
249                 // Phone app would need to be responsible for the speaker phone.
250                 // Here, we turn off the speaker if
251                 // - the phone call is the first in-coming call,
252                 // - we did not activate speaker by ourselves during the process above, and
253                 // - Bluetooth headset is not in use.
254                 if (isRealIncomingCall && !speakerActivated && isSpeakerOn(app)
255                         && !btManager.isBluetoothHeadsetAudioOn()) {
256                     // This is not an error but might cause users' confusion. Add log just in case.
257                     Log.i(LOG_TAG, "Forcing speaker off due to new incoming call...");
258                     turnOnSpeaker(app, false, true);
259                 }
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.VideoState.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             if (DBG) log("about to activate speaker");
659             // Check is phone in any dock, and turn on speaker accordingly
660             final boolean speakerActivated = activateSpeakerIfDocked(phone);
661 
662             final BluetoothManager btManager = app.getBluetoothManager();
663 
664             // See also similar logic in answerCall().
665             if (initiallyIdle && !speakerActivated && isSpeakerOn(app)
666                     && !btManager.isBluetoothHeadsetAudioOn()) {
667                 // This is not an error but might cause users' confusion. Add log just in case.
668                 Log.i(LOG_TAG, "Forcing speaker off when initiating a new outgoing call...");
669                 PhoneUtils.turnOnSpeaker(app, false, true);
670             }
671         }
672 
673         return status;
674     }
675 
toLogSafePhoneNumber(String number)676     /* package */ static String toLogSafePhoneNumber(String number) {
677         // For unknown number, log empty string.
678         if (number == null) {
679             return "";
680         }
681 
682         if (VDBG) {
683             // When VDBG is true we emit PII.
684             return number;
685         }
686 
687         // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
688         // sanitized phone numbers.
689         StringBuilder builder = new StringBuilder();
690         for (int i = 0; i < number.length(); i++) {
691             char c = number.charAt(i);
692             if (c == '-' || c == '@' || c == '.') {
693                 builder.append(c);
694             } else {
695                 builder.append('x');
696             }
697         }
698         return builder.toString();
699     }
700 
701     /**
702      * Wrapper function to control when to send an empty Flash command to the network.
703      * Mainly needed for CDMA networks, such as scenarios when we need to send a blank flash
704      * to the network prior to placing a 3-way call for it to be successful.
705      */
sendEmptyFlash(Phone phone)706     static void sendEmptyFlash(Phone phone) {
707         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
708             Call fgCall = phone.getForegroundCall();
709             if (fgCall.getState() == Call.State.ACTIVE) {
710                 // Send the empty flash
711                 if (DBG) Log.d(LOG_TAG, "onReceive: (CDMA) sending empty flash to network");
712                 switchHoldingAndActive(phone.getBackgroundCall());
713             }
714         }
715     }
716 
swap()717     static void swap() {
718         final PhoneGlobals mApp = PhoneGlobals.getInstance();
719         if (!okToSwapCalls(mApp.mCM)) {
720             // TODO: throw an error instead?
721             return;
722         }
723 
724         // Swap the fg and bg calls.
725         // In the future we may provide some way for user to choose among
726         // multiple background calls, for now, always act on the first background call.
727         PhoneUtils.switchHoldingAndActive(mApp.mCM.getFirstActiveBgCall());
728     }
729 
730     /**
731      * @param heldCall is the background call want to be swapped
732      */
switchHoldingAndActive(Call heldCall)733     static void switchHoldingAndActive(Call heldCall) {
734         log("switchHoldingAndActive()...");
735         try {
736             CallManager cm = PhoneGlobals.getInstance().mCM;
737             if (heldCall.isIdle()) {
738                 // no heldCall, so it is to hold active call
739                 cm.switchHoldingAndActive(cm.getFgPhone().getBackgroundCall());
740             } else {
741                 // has particular heldCall, so to switch
742                 cm.switchHoldingAndActive(heldCall);
743             }
744             setAudioMode(cm);
745         } catch (CallStateException ex) {
746             Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
747         }
748     }
749 
mergeCalls()750     static void mergeCalls() {
751         mergeCalls(PhoneGlobals.getInstance().mCM);
752     }
753 
mergeCalls(CallManager cm)754     static void mergeCalls(CallManager cm) {
755         int phoneType = cm.getFgPhone().getPhoneType();
756         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
757             log("mergeCalls(): CDMA...");
758             PhoneGlobals app = PhoneGlobals.getInstance();
759             if (app.cdmaPhoneCallState.getCurrentCallState()
760                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
761                 // Set the Phone Call State to conference
762                 app.cdmaPhoneCallState.setCurrentCallState(
763                         CdmaPhoneCallState.PhoneCallState.CONF_CALL);
764 
765                 // Send flash cmd
766                 // TODO: Need to change the call from switchHoldingAndActive to
767                 // something meaningful as we are not actually trying to swap calls but
768                 // instead are merging two calls by sending a Flash command.
769                 log("- sending flash...");
770                 switchHoldingAndActive(cm.getFirstActiveBgCall());
771             }
772         } else {
773             try {
774                 log("mergeCalls(): calling cm.conference()...");
775                 cm.conference(cm.getFirstActiveBgCall());
776             } catch (CallStateException ex) {
777                 Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
778             }
779         }
780     }
781 
separateCall(Connection c)782     static void separateCall(Connection c) {
783         try {
784             if (DBG) log("separateCall: " + toLogSafePhoneNumber(c.getAddress()));
785             c.separate();
786         } catch (CallStateException ex) {
787             Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
788         }
789     }
790 
791     /**
792      * Handle the MMIInitiate message and put up an alert that lets
793      * the user cancel the operation, if applicable.
794      *
795      * @param context context to get strings.
796      * @param mmiCode the MmiCode object being started.
797      * @param buttonCallbackMessage message to post when button is clicked.
798      * @param previousAlert a previous alert used in this activity.
799      * @return the dialog handle
800      */
displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)801     static Dialog displayMMIInitiate(Context context,
802                                           MmiCode mmiCode,
803                                           Message buttonCallbackMessage,
804                                           Dialog previousAlert) {
805         if (DBG) log("displayMMIInitiate: " + mmiCode);
806         if (previousAlert != null) {
807             previousAlert.dismiss();
808         }
809 
810         // The UI paradigm we are using now requests that all dialogs have
811         // user interaction, and that any other messages to the user should
812         // be by way of Toasts.
813         //
814         // In adhering to this request, all MMI initiating "OK" dialogs
815         // (non-cancelable MMIs) that end up being closed when the MMI
816         // completes (thereby showing a completion dialog) are being
817         // replaced with Toasts.
818         //
819         // As a side effect, moving to Toasts for the non-cancelable MMIs
820         // also means that buttonCallbackMessage (which was tied into "OK")
821         // is no longer invokable for these dialogs.  This is not a problem
822         // since the only callback messages we supported were for cancelable
823         // MMIs anyway.
824         //
825         // A cancelable MMI is really just a USSD request. The term
826         // "cancelable" here means that we can cancel the request when the
827         // system prompts us for a response, NOT while the network is
828         // processing the MMI request.  Any request to cancel a USSD while
829         // the network is NOT ready for a response may be ignored.
830         //
831         // With this in mind, we replace the cancelable alert dialog with
832         // a progress dialog, displayed until we receive a request from
833         // the the network.  For more information, please see the comments
834         // in the displayMMIComplete() method below.
835         //
836         // Anything that is NOT a USSD request is a normal MMI request,
837         // which will bring up a toast (desribed above).
838 
839         boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
840 
841         if (!isCancelable) {
842             if (DBG) log("not a USSD code, displaying status toast.");
843             CharSequence text = context.getText(R.string.mmiStarted);
844             Toast.makeText(context, text, Toast.LENGTH_SHORT)
845                 .show();
846             return null;
847         } else {
848             if (DBG) log("running USSD code, displaying indeterminate progress.");
849 
850             // create the indeterminate progress dialog and display it.
851             ProgressDialog pd = new ProgressDialog(context);
852             pd.setMessage(context.getText(R.string.ussdRunning));
853             pd.setCancelable(false);
854             pd.setIndeterminate(true);
855             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
856 
857             pd.show();
858 
859             return pd;
860         }
861 
862     }
863 
864     /**
865      * Handle the MMIComplete message and fire off an intent to display
866      * the message.
867      *
868      * @param context context to get strings.
869      * @param mmiCode MMI result.
870      * @param previousAlert a previous alert used in this activity.
871      */
displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)872     static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
873             Message dismissCallbackMessage,
874             AlertDialog previousAlert) {
875         final PhoneGlobals app = PhoneGlobals.getInstance();
876         CharSequence text;
877         int title = 0;  // title for the progress dialog, if needed.
878         MmiCode.State state = mmiCode.getState();
879 
880         if (DBG) log("displayMMIComplete: state=" + state);
881 
882         switch (state) {
883             case PENDING:
884                 // USSD code asking for feedback from user.
885                 text = mmiCode.getMessage();
886                 if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
887                 break;
888             case CANCELLED:
889                 text = null;
890                 break;
891             case COMPLETE:
892                 if (app.getPUKEntryActivity() != null) {
893                     // if an attempt to unPUK the device was made, we specify
894                     // the title and the message here.
895                     title = com.android.internal.R.string.PinMmi;
896                     text = context.getText(R.string.puk_unlocked);
897                     break;
898                 }
899                 // All other conditions for the COMPLETE mmi state will cause
900                 // the case to fall through to message logic in common with
901                 // the FAILED case.
902 
903             case FAILED:
904                 text = mmiCode.getMessage();
905                 if (DBG) log("- using text from MMI message: '" + text + "'");
906                 break;
907             default:
908                 throw new IllegalStateException("Unexpected MmiCode state: " + state);
909         }
910 
911         if (previousAlert != null) {
912             previousAlert.dismiss();
913         }
914 
915         // Check to see if a UI exists for the PUK activation.  If it does
916         // exist, then it indicates that we're trying to unblock the PUK.
917         if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
918             if (DBG) log("displaying PUK unblocking progress dialog.");
919 
920             // create the progress dialog, make sure the flags and type are
921             // set correctly.
922             ProgressDialog pd = new ProgressDialog(app);
923             pd.setTitle(title);
924             pd.setMessage(text);
925             pd.setCancelable(false);
926             pd.setIndeterminate(true);
927             pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
928             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
929 
930             // display the dialog
931             pd.show();
932 
933             // indicate to the Phone app that the progress dialog has
934             // been assigned for the PUK unlock / SIM READY process.
935             app.setPukEntryProgressDialog(pd);
936 
937         } else {
938             // In case of failure to unlock, we'll need to reset the
939             // PUK unlock activity, so that the user may try again.
940             if (app.getPUKEntryActivity() != null) {
941                 app.setPukEntryActivity(null);
942             }
943 
944             // A USSD in a pending state means that it is still
945             // interacting with the user.
946             if (state != MmiCode.State.PENDING) {
947                 if (DBG) log("MMI code has finished running.");
948 
949                 if (DBG) log("Extended NW displayMMIInitiate (" + text + ")");
950                 if (text == null || text.length() == 0)
951                     return;
952 
953                 // displaying system alert dialog on the screen instead of
954                 // using another activity to display the message.  This
955                 // places the message at the forefront of the UI.
956 
957                 if (sUssdDialog == null) {
958                     sUssdDialog = new AlertDialog.Builder(context)
959                             .setPositiveButton(R.string.ok, null)
960                             .setCancelable(true)
961                             .setOnDismissListener(new DialogInterface.OnDismissListener() {
962                                 @Override
963                                 public void onDismiss(DialogInterface dialog) {
964                                     sUssdMsg.setLength(0);
965                                 }
966                             })
967                             .create();
968 
969                     sUssdDialog.getWindow().setType(
970                             WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
971                     sUssdDialog.getWindow().addFlags(
972                             WindowManager.LayoutParams.FLAG_DIM_BEHIND);
973                 }
974                 if (sUssdMsg.length() != 0) {
975                     sUssdMsg
976                             .insert(0, "\n")
977                             .insert(0, app.getResources().getString(R.string.ussd_dialog_sep))
978                             .insert(0, "\n");
979                 }
980                 sUssdMsg.insert(0, text);
981                 sUssdDialog.setMessage(sUssdMsg.toString());
982                 sUssdDialog.show();
983             } else {
984                 if (DBG) log("USSD code has requested user input. Constructing input dialog.");
985 
986                 // USSD MMI code that is interacting with the user.  The
987                 // basic set of steps is this:
988                 //   1. User enters a USSD request
989                 //   2. We recognize the request and displayMMIInitiate
990                 //      (above) creates a progress dialog.
991                 //   3. Request returns and we get a PENDING or COMPLETE
992                 //      message.
993                 //   4. These MMI messages are caught in the PhoneApp
994                 //      (onMMIComplete) and the InCallScreen
995                 //      (mHandler.handleMessage) which bring up this dialog
996                 //      and closes the original progress dialog,
997                 //      respectively.
998                 //   5. If the message is anything other than PENDING,
999                 //      we are done, and the alert dialog (directly above)
1000                 //      displays the outcome.
1001                 //   6. If the network is requesting more information from
1002                 //      the user, the MMI will be in a PENDING state, and
1003                 //      we display this dialog with the message.
1004                 //   7. User input, or cancel requests result in a return
1005                 //      to step 1.  Keep in mind that this is the only
1006                 //      time that a USSD should be canceled.
1007 
1008                 // inflate the layout with the scrolling text area for the dialog.
1009                 LayoutInflater inflater = (LayoutInflater) context.getSystemService(
1010                         Context.LAYOUT_INFLATER_SERVICE);
1011                 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
1012 
1013                 // get the input field.
1014                 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
1015 
1016                 // specify the dialog's click listener, with SEND and CANCEL logic.
1017                 final DialogInterface.OnClickListener mUSSDDialogListener =
1018                     new DialogInterface.OnClickListener() {
1019                         public void onClick(DialogInterface dialog, int whichButton) {
1020                             switch (whichButton) {
1021                                 case DialogInterface.BUTTON_POSITIVE:
1022                                     // As per spec 24.080, valid length of ussd string
1023                                     // is 1 - 160. If length is out of the range then
1024                                     // display toast message & Cancel MMI operation.
1025                                     if (inputText.length() < MIN_USSD_LEN
1026                                             || inputText.length() > MAX_USSD_LEN) {
1027                                         Toast.makeText(app,
1028                                                 app.getResources().getString(R.string.enter_input,
1029                                                 MIN_USSD_LEN, MAX_USSD_LEN),
1030                                                 Toast.LENGTH_LONG).show();
1031                                         if (mmiCode.isCancelable()) {
1032                                             mmiCode.cancel();
1033                                         }
1034                                     } else {
1035                                         phone.sendUssdResponse(inputText.getText().toString());
1036                                     }
1037                                     break;
1038                                 case DialogInterface.BUTTON_NEGATIVE:
1039                                     if (mmiCode.isCancelable()) {
1040                                         mmiCode.cancel();
1041                                     }
1042                                     break;
1043                             }
1044                         }
1045                     };
1046 
1047                 // build the dialog
1048                 final AlertDialog newDialog = new AlertDialog.Builder(context)
1049                         .setMessage(text)
1050                         .setView(dialogView)
1051                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
1052                         .setNegativeButton(R.string.cancel, mUSSDDialogListener)
1053                         .setCancelable(false)
1054                         .create();
1055 
1056                 // attach the key listener to the dialog's input field and make
1057                 // sure focus is set.
1058                 final View.OnKeyListener mUSSDDialogInputListener =
1059                     new View.OnKeyListener() {
1060                         public boolean onKey(View v, int keyCode, KeyEvent event) {
1061                             switch (keyCode) {
1062                                 case KeyEvent.KEYCODE_CALL:
1063                                 case KeyEvent.KEYCODE_ENTER:
1064                                     if(event.getAction() == KeyEvent.ACTION_DOWN) {
1065                                         phone.sendUssdResponse(inputText.getText().toString());
1066                                         newDialog.dismiss();
1067                                     }
1068                                     return true;
1069                             }
1070                             return false;
1071                         }
1072                     };
1073                 inputText.setOnKeyListener(mUSSDDialogInputListener);
1074                 inputText.requestFocus();
1075 
1076                 // set the window properties of the dialog
1077                 newDialog.getWindow().setType(
1078                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
1079                 newDialog.getWindow().addFlags(
1080                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
1081 
1082                 // now show the dialog!
1083                 newDialog.show();
1084             }
1085         }
1086     }
1087 
1088     /**
1089      * Cancels the current pending MMI operation, if applicable.
1090      * @return true if we canceled an MMI operation, or false
1091      *         if the current pending MMI wasn't cancelable
1092      *         or if there was no current pending MMI at all.
1093      *
1094      * @see displayMMIInitiate
1095      */
cancelMmiCode(Phone phone)1096     static boolean cancelMmiCode(Phone phone) {
1097         List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
1098         int count = pendingMmis.size();
1099         if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
1100 
1101         boolean canceled = false;
1102         if (count > 0) {
1103             // assume that we only have one pending MMI operation active at a time.
1104             // I don't think it's possible to enter multiple MMI codes concurrently
1105             // in the phone UI, because during the MMI operation, an Alert panel
1106             // is displayed, which prevents more MMI code from being entered.
1107             MmiCode mmiCode = pendingMmis.get(0);
1108             if (mmiCode.isCancelable()) {
1109                 mmiCode.cancel();
1110                 canceled = true;
1111             }
1112         }
1113         return canceled;
1114     }
1115 
1116     public static class VoiceMailNumberMissingException extends Exception {
VoiceMailNumberMissingException()1117         VoiceMailNumberMissingException() {
1118             super();
1119         }
1120 
VoiceMailNumberMissingException(String msg)1121         VoiceMailNumberMissingException(String msg) {
1122             super(msg);
1123         }
1124     }
1125 
1126     /**
1127      * Given an Intent (which is presumably the ACTION_CALL intent that
1128      * initiated this outgoing call), figure out the actual phone number we
1129      * should dial.
1130      *
1131      * Note that the returned "number" may actually be a SIP address,
1132      * if the specified intent contains a sip: URI.
1133      *
1134      * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(),
1135      * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra.
1136      * (That extra, if present, tells us the exact string to pass down to the
1137      * telephony layer.  It's guaranteed to be safe to dial: it's either a PSTN
1138      * phone number with separators and keypad letters stripped out, or a raw
1139      * unencoded SIP address.)
1140      *
1141      * @return the phone number corresponding to the specified Intent, or null
1142      *   if the Intent has no action or if the intent's data is malformed or
1143      *   missing.
1144      *
1145      * @throws VoiceMailNumberMissingException if the intent
1146      *   contains a "voicemail" URI, but there's no voicemail
1147      *   number configured on the device.
1148      */
getInitialNumber(Intent intent)1149     public static String getInitialNumber(Intent intent)
1150             throws PhoneUtils.VoiceMailNumberMissingException {
1151         if (DBG) log("getInitialNumber(): " + intent);
1152 
1153         String action = intent.getAction();
1154         if (TextUtils.isEmpty(action)) {
1155             return null;
1156         }
1157 
1158         // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone
1159         // number from there.  (That extra takes precedence over the actual data
1160         // included in the intent.)
1161         if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) {
1162             String actualNumberToDial =
1163                     intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL);
1164             if (DBG) {
1165                 log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '"
1166                         + toLogSafePhoneNumber(actualNumberToDial) + "'");
1167             }
1168             return actualNumberToDial;
1169         }
1170 
1171         return getNumberFromIntent(PhoneGlobals.getInstance(), intent);
1172     }
1173 
1174     /**
1175      * Gets the phone number to be called from an intent.  Requires a Context
1176      * to access the contacts database, and a Phone to access the voicemail
1177      * number.
1178      *
1179      * <p>If <code>phone</code> is <code>null</code>, the function will return
1180      * <code>null</code> for <code>voicemail:</code> URIs;
1181      * if <code>context</code> is <code>null</code>, the function will return
1182      * <code>null</code> for person/phone URIs.</p>
1183      *
1184      * <p>If the intent contains a <code>sip:</code> URI, the returned
1185      * "number" is actually the SIP address.
1186      *
1187      * @param context a context to use (or
1188      * @param intent the intent
1189      *
1190      * @throws VoiceMailNumberMissingException if <code>intent</code> contains
1191      *         a <code>voicemail:</code> URI, but <code>phone</code> does not
1192      *         have a voicemail number set.
1193      *
1194      * @return the phone number (or SIP address) that would be called by the intent,
1195      *         or <code>null</code> if the number cannot be found.
1196      */
getNumberFromIntent(Context context, Intent intent)1197     private static String getNumberFromIntent(Context context, Intent intent)
1198             throws VoiceMailNumberMissingException {
1199         Uri uri = intent.getData();
1200         String scheme = uri.getScheme();
1201 
1202         // The sip: scheme is simple: just treat the rest of the URI as a
1203         // SIP address.
1204         if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
1205             return uri.getSchemeSpecificPart();
1206         }
1207 
1208         // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle
1209         // the other cases (i.e. tel: and voicemail: and contact: URIs.)
1210 
1211         final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
1212 
1213         // Check for a voicemail-dialing request.  If the voicemail number is
1214         // empty, throw a VoiceMailNumberMissingException.
1215         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) &&
1216                 (number == null || TextUtils.isEmpty(number)))
1217             throw new VoiceMailNumberMissingException();
1218 
1219         return number;
1220     }
1221 
1222     /**
1223      * Returns the caller-id info corresponding to the specified Connection.
1224      * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
1225      * extract a phone number from the specified Connection, and feed that
1226      * number into CallerInfo.getCallerInfo().)
1227      *
1228      * The returned CallerInfo may be null in certain error cases, like if the
1229      * specified Connection was null, or if we weren't able to get a valid
1230      * phone number from the Connection.
1231      *
1232      * Finally, if the getCallerInfo() call did succeed, we save the resulting
1233      * CallerInfo object in the "userData" field of the Connection.
1234      *
1235      * NOTE: This API should be avoided, with preference given to the
1236      * asynchronous startGetCallerInfo API.
1237      */
getCallerInfo(Context context, Connection c)1238     static CallerInfo getCallerInfo(Context context, Connection c) {
1239         CallerInfo info = null;
1240 
1241         if (c != null) {
1242             //See if there is a URI attached.  If there is, this means
1243             //that there is no CallerInfo queried yet, so we'll need to
1244             //replace the URI with a full CallerInfo object.
1245             Object userDataObject = c.getUserData();
1246             if (userDataObject instanceof Uri) {
1247                 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
1248                 if (info != null) {
1249                     c.setUserData(info);
1250                 }
1251             } else {
1252                 if (userDataObject instanceof CallerInfoToken) {
1253                     //temporary result, while query is running
1254                     info = ((CallerInfoToken) userDataObject).currentInfo;
1255                 } else {
1256                     //final query result
1257                     info = (CallerInfo) userDataObject;
1258                 }
1259                 if (info == null) {
1260                     // No URI, or Existing CallerInfo, so we'll have to make do with
1261                     // querying a new CallerInfo using the connection's phone number.
1262                     String number = c.getAddress();
1263 
1264                     if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number));
1265 
1266                     if (!TextUtils.isEmpty(number)) {
1267                         info = CallerInfo.getCallerInfo(context, number);
1268                         if (info != null) {
1269                             c.setUserData(info);
1270                         }
1271                     }
1272                 }
1273             }
1274         }
1275         return info;
1276     }
1277 
1278     /**
1279      * Class returned by the startGetCallerInfo call to package a temporary
1280      * CallerInfo Object, to be superceded by the CallerInfo Object passed
1281      * into the listener when the query with token mAsyncQueryToken is complete.
1282      */
1283     public static class CallerInfoToken {
1284         /**indicates that there will no longer be updates to this request.*/
1285         public boolean isFinal;
1286 
1287         public CallerInfo currentInfo;
1288         public CallerInfoAsyncQuery asyncQuery;
1289     }
1290 
1291     /**
1292      * Start a CallerInfo Query based on the earliest connection in the call.
1293      */
startGetCallerInfo(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1294     static CallerInfoToken startGetCallerInfo(Context context, Call call,
1295             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1296         Connection conn = null;
1297         int phoneType = call.getPhone().getPhoneType();
1298         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1299             conn = call.getLatestConnection();
1300         } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1301                 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
1302                 || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
1303                 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
1304             conn = call.getEarliestConnection();
1305         } else {
1306             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1307         }
1308 
1309         return startGetCallerInfo(context, conn, listener, cookie);
1310     }
1311 
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1312     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1313             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1314         return startGetCallerInfo(context, c, listener, cookie, null);
1315     }
1316 
1317     /**
1318      * place a temporary callerinfo object in the hands of the caller and notify
1319      * caller when the actual query is done.
1320      */
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)1321     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1322             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie,
1323             RawGatewayInfo info) {
1324         CallerInfoToken cit;
1325 
1326         if (c == null) {
1327             //TODO: perhaps throw an exception here.
1328             cit = new CallerInfoToken();
1329             cit.asyncQuery = null;
1330             return cit;
1331         }
1332 
1333         Object userDataObject = c.getUserData();
1334 
1335         // There are now 3 states for the Connection's userData object:
1336         //
1337         //   (1) Uri - query has not been executed yet
1338         //
1339         //   (2) CallerInfoToken - query is executing, but has not completed.
1340         //
1341         //   (3) CallerInfo - query has executed.
1342         //
1343         // In each case we have slightly different behaviour:
1344         //   1. If the query has not been executed yet (Uri or null), we start
1345         //      query execution asynchronously, and note it by attaching a
1346         //      CallerInfoToken as the userData.
1347         //   2. If the query is executing (CallerInfoToken), we've essentially
1348         //      reached a state where we've received multiple requests for the
1349         //      same callerInfo.  That means that once the query is complete,
1350         //      we'll need to execute the additional listener requested.
1351         //   3. If the query has already been executed (CallerInfo), we just
1352         //      return the CallerInfo object as expected.
1353         //   4. Regarding isFinal - there are cases where the CallerInfo object
1354         //      will not be attached, like when the number is empty (caller id
1355         //      blocking).  This flag is used to indicate that the
1356         //      CallerInfoToken object is going to be permanent since no
1357         //      query results will be returned.  In the case where a query
1358         //      has been completed, this flag is used to indicate to the caller
1359         //      that the data will not be updated since it is valid.
1360         //
1361         //      Note: For the case where a number is NOT retrievable, we leave
1362         //      the CallerInfo as null in the CallerInfoToken.  This is
1363         //      something of a departure from the original code, since the old
1364         //      code manufactured a CallerInfo object regardless of the query
1365         //      outcome.  From now on, we will append an empty CallerInfo
1366         //      object, to mirror previous behaviour, and to avoid Null Pointer
1367         //      Exceptions.
1368 
1369         if (userDataObject instanceof Uri) {
1370             // State (1): query has not been executed yet
1371 
1372             //create a dummy callerinfo, populate with what we know from URI.
1373             cit = new CallerInfoToken();
1374             cit.currentInfo = new CallerInfo();
1375             cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1376                     (Uri) userDataObject, sCallerInfoQueryListener, c);
1377             cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1378             cit.isFinal = false;
1379 
1380             c.setUserData(cit);
1381 
1382             if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
1383 
1384         } else if (userDataObject == null) {
1385             // No URI, or Existing CallerInfo, so we'll have to make do with
1386             // querying a new CallerInfo using the connection's phone number.
1387             String number = c.getAddress();
1388 
1389             if (info != null && info != CallGatewayManager.EMPTY_INFO) {
1390                 // Gateway number, the connection number is actually the gateway number.
1391                 // need to lookup via dialed number.
1392                 number = info.trueNumber;
1393             }
1394 
1395             if (DBG) {
1396                 log("PhoneUtils.startGetCallerInfo: new query for phone number...");
1397                 log("- number (address): " + toLogSafePhoneNumber(number));
1398                 log("- c: " + c);
1399                 log("- phone: " + c.getCall().getPhone());
1400                 int phoneType = c.getCall().getPhone().getPhoneType();
1401                 log("- phoneType: " + phoneType);
1402                 switch (phoneType) {
1403                     case PhoneConstants.PHONE_TYPE_NONE: log("  ==> PHONE_TYPE_NONE"); break;
1404                     case PhoneConstants.PHONE_TYPE_GSM: log("  ==> PHONE_TYPE_GSM"); break;
1405                     case PhoneConstants.PHONE_TYPE_IMS: log("  ==> PHONE_TYPE_IMS"); break;
1406                     case PhoneConstants.PHONE_TYPE_CDMA: log("  ==> PHONE_TYPE_CDMA"); break;
1407                     case PhoneConstants.PHONE_TYPE_SIP: log("  ==> PHONE_TYPE_SIP"); break;
1408                     case PhoneConstants.PHONE_TYPE_THIRD_PARTY:
1409                         log("  ==> PHONE_TYPE_THIRD_PARTY");
1410                         break;
1411                     default: log("  ==> Unknown phone type"); break;
1412                 }
1413             }
1414 
1415             cit = new CallerInfoToken();
1416             cit.currentInfo = new CallerInfo();
1417 
1418             // Store CNAP information retrieved from the Connection (we want to do this
1419             // here regardless of whether the number is empty or not).
1420             cit.currentInfo.cnapName =  c.getCnapName();
1421             cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
1422                                                              // by ContactInfo later
1423             cit.currentInfo.numberPresentation = c.getNumberPresentation();
1424             cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1425 
1426             if (VDBG) {
1427                 log("startGetCallerInfo: number = " + number);
1428                 log("startGetCallerInfo: CNAP Info from FW(1): name="
1429                     + cit.currentInfo.cnapName
1430                     + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1431             }
1432 
1433             // handling case where number is null (caller id hidden) as well.
1434             if (!TextUtils.isEmpty(number)) {
1435                 // Check for special CNAP cases and modify the CallerInfo accordingly
1436                 // to be sure we keep the right information to display/log later
1437                 number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
1438                         cit.currentInfo.numberPresentation);
1439 
1440                 cit.currentInfo.phoneNumber = number;
1441                 // For scenarios where we may receive a valid number from the network but a
1442                 // restricted/unavailable presentation, we do not want to perform a contact query
1443                 // (see note on isFinal above). So we set isFinal to true here as well.
1444                 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1445                     cit.isFinal = true;
1446                 } else {
1447                     if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()...");
1448                     cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1449                             number, sCallerInfoQueryListener, c);
1450                     cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1451                     cit.isFinal = false;
1452                 }
1453             } else {
1454                 // This is the case where we are querying on a number that
1455                 // is null or empty, like a caller whose caller id is
1456                 // blocked or empty (CLIR).  The previous behaviour was to
1457                 // throw a null CallerInfo object back to the user, but
1458                 // this departure is somewhat cleaner.
1459                 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
1460                 cit.isFinal = true; // please see note on isFinal, above.
1461             }
1462 
1463             c.setUserData(cit);
1464 
1465             if (DBG) {
1466                 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number));
1467             }
1468 
1469         } else if (userDataObject instanceof CallerInfoToken) {
1470             // State (2): query is executing, but has not completed.
1471 
1472             // just tack on this listener to the queue.
1473             cit = (CallerInfoToken) userDataObject;
1474 
1475             // handling case where number is null (caller id hidden) as well.
1476             if (cit.asyncQuery != null) {
1477                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1478 
1479                 if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
1480                         listener.getClass().toString());
1481             } else {
1482                 // handling case where number/name gets updated later on by the network
1483                 String updatedNumber = c.getAddress();
1484 
1485                 if (info != null) {
1486                     // Gateway number, the connection number is actually the gateway number.
1487                     // need to lookup via dialed number.
1488                     updatedNumber = info.trueNumber;
1489                 }
1490 
1491                 if (DBG) {
1492                     log("startGetCallerInfo: updatedNumber initially = "
1493                             + toLogSafePhoneNumber(updatedNumber));
1494                 }
1495                 if (!TextUtils.isEmpty(updatedNumber)) {
1496                     // Store CNAP information retrieved from the Connection
1497                     cit.currentInfo.cnapName =  c.getCnapName();
1498                     // This can still get overwritten by ContactInfo
1499                     cit.currentInfo.name = cit.currentInfo.cnapName;
1500                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1501                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1502 
1503                     updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
1504                             updatedNumber, cit.currentInfo.numberPresentation);
1505 
1506                     cit.currentInfo.phoneNumber = updatedNumber;
1507                     if (DBG) {
1508                         log("startGetCallerInfo: updatedNumber="
1509                                 + toLogSafePhoneNumber(updatedNumber));
1510                     }
1511                     if (VDBG) {
1512                         log("startGetCallerInfo: CNAP Info from FW(2): name="
1513                                 + cit.currentInfo.cnapName
1514                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1515                     } else if (DBG) {
1516                         log("startGetCallerInfo: CNAP Info from FW(2)");
1517                     }
1518                     // For scenarios where we may receive a valid number from the network but a
1519                     // restricted/unavailable presentation, we do not want to perform a contact query
1520                     // (see note on isFinal above). So we set isFinal to true here as well.
1521                     if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1522                         cit.isFinal = true;
1523                     } else {
1524                         cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1525                                 updatedNumber, sCallerInfoQueryListener, c);
1526                         cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1527                         cit.isFinal = false;
1528                     }
1529                 } else {
1530                     if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
1531                     if (cit.currentInfo == null) {
1532                         cit.currentInfo = new CallerInfo();
1533                     }
1534                     // Store CNAP information retrieved from the Connection
1535                     cit.currentInfo.cnapName = c.getCnapName();  // This can still get
1536                                                                  // overwritten by ContactInfo
1537                     cit.currentInfo.name = cit.currentInfo.cnapName;
1538                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
1539                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1540 
1541                     if (VDBG) {
1542                         log("startGetCallerInfo: CNAP Info from FW(3): name="
1543                                 + cit.currentInfo.cnapName
1544                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1545                     } else if (DBG) {
1546                         log("startGetCallerInfo: CNAP Info from FW(3)");
1547                     }
1548                     cit.isFinal = true; // please see note on isFinal, above.
1549                 }
1550             }
1551         } else {
1552             // State (3): query is complete.
1553 
1554             // The connection's userDataObject is a full-fledged
1555             // CallerInfo instance.  Wrap it in a CallerInfoToken and
1556             // return it to the user.
1557 
1558             cit = new CallerInfoToken();
1559             cit.currentInfo = (CallerInfo) userDataObject;
1560             cit.asyncQuery = null;
1561             cit.isFinal = true;
1562             // since the query is already done, call the listener.
1563             if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
1564             if (DBG) log("==> cit.currentInfo = " + cit.currentInfo);
1565         }
1566         return cit;
1567     }
1568 
1569     /**
1570      * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that
1571      * we use with all our CallerInfoAsyncQuery.startQuery() requests.
1572      */
1573     private static final int QUERY_TOKEN = -1;
1574     static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
1575         new CallerInfoAsyncQuery.OnQueryCompleteListener () {
1576             /**
1577              * When the query completes, we stash the resulting CallerInfo
1578              * object away in the Connection's "userData" (where it will
1579              * later be retrieved by the in-call UI.)
1580              */
1581             public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
1582                 if (DBG) log("query complete, updating connection.userdata");
1583                 Connection conn = (Connection) cookie;
1584 
1585                 // Added a check if CallerInfo is coming from ContactInfo or from Connection.
1586                 // If no ContactInfo, then we want to use CNAP information coming from network
1587                 if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
1588                 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
1589                     // If the number presentation has not been set by
1590                     // the ContactInfo, use the one from the
1591                     // connection.
1592 
1593                     // TODO: Need a new util method to merge the info
1594                     // from the Connection in a CallerInfo object.
1595                     // Here 'ci' is a new CallerInfo instance read
1596                     // from the DB. It has lost all the connection
1597                     // info preset before the query (see PhoneUtils
1598                     // line 1334). We should have a method to merge
1599                     // back into this new instance the info from the
1600                     // connection object not set by the DB. If the
1601                     // Connection already has a CallerInfo instance in
1602                     // userData, then we could use this instance to
1603                     // fill 'ci' in. The same routine could be used in
1604                     // PhoneUtils.
1605                     if (0 == ci.numberPresentation) {
1606                         ci.numberPresentation = conn.getNumberPresentation();
1607                     }
1608                 } else {
1609                     // No matching contact was found for this number.
1610                     // Return a new CallerInfo based solely on the CNAP
1611                     // information from the network.
1612 
1613                     CallerInfo newCi = getCallerInfo(null, conn);
1614 
1615                     // ...but copy over the (few) things we care about
1616                     // from the original CallerInfo object:
1617                     if (newCi != null) {
1618                         newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1619                         newCi.geoDescription = ci.geoDescription; // To get geo description string
1620                         ci = newCi;
1621                     }
1622                 }
1623 
1624                 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection...");
1625                 conn.setUserData(ci);
1626             }
1627         };
1628 
1629 
1630     /**
1631      * Returns a single "name" for the specified given a CallerInfo object.
1632      * If the name is null, return defaultString as the default value, usually
1633      * context.getString(R.string.unknown).
1634      */
getCompactNameFromCallerInfo(CallerInfo ci, Context context)1635     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1636         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1637 
1638         String compactName = null;
1639         if (ci != null) {
1640             if (TextUtils.isEmpty(ci.name)) {
1641                 // Perform any modifications for special CNAP cases to
1642                 // the phone number being displayed, if applicable.
1643                 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber,
1644                                                         ci.numberPresentation);
1645             } else {
1646                 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795.
1647                 compactName = ci.name;
1648             }
1649         }
1650 
1651         if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1652             // If we're still null/empty here, then check if we have a presentation
1653             // string that takes precedence that we could return, otherwise display
1654             // "unknown" string.
1655             if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
1656                 compactName = context.getString(R.string.private_num);
1657             } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) {
1658                 compactName = context.getString(R.string.payphone);
1659             } else {
1660                 compactName = context.getString(R.string.unknown);
1661             }
1662         }
1663         if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
1664         return compactName;
1665     }
1666 
1667     /**
1668      * Returns true if the specified Call is a "conference call", meaning
1669      * that it owns more than one Connection object.  This information is
1670      * used to trigger certain UI changes that appear when a conference
1671      * call is active (like displaying the label "Conference call", and
1672      * enabling the "Manage conference" UI.)
1673      *
1674      * Watch out: This method simply checks the number of Connections,
1675      * *not* their states.  So if a Call has (for example) one ACTIVE
1676      * connection and one DISCONNECTED connection, this method will return
1677      * true (which is unintuitive, since the Call isn't *really* a
1678      * conference call any more.)
1679      *
1680      * @return true if the specified call has more than one connection (in any state.)
1681      */
isConferenceCall(Call call)1682     static boolean isConferenceCall(Call call) {
1683         // CDMA phones don't have the same concept of "conference call" as
1684         // GSM phones do; there's no special "conference call" state of
1685         // the UI or a "manage conference" function.  (Instead, when
1686         // you're in a 3-way call, all we can do is display the "generic"
1687         // state of the UI.)  So as far as the in-call UI is concerned,
1688         // Conference corresponds to generic display.
1689         final PhoneGlobals app = PhoneGlobals.getInstance();
1690         int phoneType = call.getPhone().getPhoneType();
1691         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1692             CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
1693             if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL)
1694                     || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1695                     && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) {
1696                 return true;
1697             }
1698         } else {
1699             List<Connection> connections = call.getConnections();
1700             if (connections != null && connections.size() > 1) {
1701                 return true;
1702             }
1703         }
1704         return false;
1705 
1706         // TODO: We may still want to change the semantics of this method
1707         // to say that a given call is only really a conference call if
1708         // the number of ACTIVE connections, not the total number of
1709         // connections, is greater than one.  (See warning comment in the
1710         // javadoc above.)
1711         // Here's an implementation of that:
1712         //        if (connections == null) {
1713         //            return false;
1714         //        }
1715         //        int numActiveConnections = 0;
1716         //        for (Connection conn : connections) {
1717         //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1718         //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1719         //            if (numActiveConnections > 1) {
1720         //                return true;
1721         //            }
1722         //        }
1723         //        return false;
1724     }
1725 
1726     /**
1727      * Launch the Dialer to start a new call.
1728      * This is just a wrapper around the ACTION_DIAL intent.
1729      */
startNewCall(final CallManager cm)1730     /* package */ static boolean startNewCall(final CallManager cm) {
1731         final PhoneGlobals app = PhoneGlobals.getInstance();
1732 
1733         // Sanity-check that this is OK given the current state of the phone.
1734         if (!okToAddCall(cm)) {
1735             Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1736             dumpCallManager();
1737             return false;
1738         }
1739 
1740         Intent intent = new Intent(Intent.ACTION_DIAL);
1741         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1742 
1743         // when we request the dialer come up, we also want to inform
1744         // it that we're going through the "add call" option from the
1745         // InCallScreen / PhoneUtils.
1746         intent.putExtra(ADD_CALL_MODE_KEY, true);
1747         try {
1748             app.startActivity(intent);
1749         } catch (ActivityNotFoundException e) {
1750             // This is rather rare but possible.
1751             // Note: this method is used even when the phone is encrypted. At that moment
1752             // the system may not find any Activity which can accept this Intent.
1753             Log.e(LOG_TAG, "Activity for adding calls isn't found.");
1754             return false;
1755         }
1756 
1757         return true;
1758     }
1759 
1760     /**
1761      * Turns on/off speaker.
1762      *
1763      * @param context Context
1764      * @param flag True when speaker should be on. False otherwise.
1765      * @param store True when the settings should be stored in the device.
1766      */
turnOnSpeaker(Context context, boolean flag, boolean store)1767     /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) {
1768         if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
1769         final PhoneGlobals app = PhoneGlobals.getInstance();
1770 
1771         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1772         audioManager.setSpeakerphoneOn(flag);
1773 
1774         // record the speaker-enable value
1775         if (store) {
1776             sIsSpeakerEnabled = flag;
1777         }
1778 
1779         // We also need to make a fresh call to PhoneApp.updateWakeState()
1780         // any time the speaker state changes, since the screen timeout is
1781         // sometimes different depending on whether or not the speaker is
1782         // in use.
1783         app.updateWakeState();
1784 
1785         app.mCM.setEchoSuppressionEnabled();
1786     }
1787 
1788     /**
1789      * Restore the speaker mode, called after a wired headset disconnect
1790      * event.
1791      */
restoreSpeakerMode(Context context)1792     static void restoreSpeakerMode(Context context) {
1793         if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1794 
1795         // change the mode if needed.
1796         if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1797             turnOnSpeaker(context, sIsSpeakerEnabled, false);
1798         }
1799     }
1800 
isSpeakerOn(Context context)1801     static boolean isSpeakerOn(Context context) {
1802         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1803         return audioManager.isSpeakerphoneOn();
1804     }
1805 
1806 
turnOnNoiseSuppression(Context context, boolean flag, boolean store)1807     static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) {
1808         if (DBG) log("turnOnNoiseSuppression: " + flag);
1809         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1810 
1811         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1812             return;
1813         }
1814 
1815         if (flag) {
1816             audioManager.setParameters("noise_suppression=auto");
1817         } else {
1818             audioManager.setParameters("noise_suppression=off");
1819         }
1820 
1821         // record the speaker-enable value
1822         if (store) {
1823             sIsNoiseSuppressionEnabled = flag;
1824         }
1825 
1826         // TODO: implement and manage ICON
1827 
1828     }
1829 
restoreNoiseSuppression(Context context)1830     static void restoreNoiseSuppression(Context context) {
1831         if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled);
1832 
1833         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1834             return;
1835         }
1836 
1837         // change the mode if needed.
1838         if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) {
1839             turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false);
1840         }
1841     }
1842 
isNoiseSuppressionOn(Context context)1843     static boolean isNoiseSuppressionOn(Context context) {
1844 
1845         if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
1846             return false;
1847         }
1848 
1849         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1850         String noiseSuppression = audioManager.getParameters("noise_suppression");
1851         if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression);
1852         if (noiseSuppression.contains("off")) {
1853             return false;
1854         } else {
1855             return true;
1856         }
1857     }
1858 
isInEmergencyCall(CallManager cm)1859     static boolean isInEmergencyCall(CallManager cm) {
1860         for (Connection cn : cm.getActiveFgCall().getConnections()) {
1861             if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
1862                     cn.getAddress())) {
1863                 return true;
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.gsm.GsmCall#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     * This function is called when phone answers or places a call.
2176     * Check if the phone is in a car dock or desk dock.
2177     * If yes, turn on the speaker, when no wired or BT headsets are connected.
2178     * Otherwise do nothing.
2179     * @return true if activated
2180     */
activateSpeakerIfDocked(Phone phone)2181     private static boolean activateSpeakerIfDocked(Phone phone) {
2182         if (DBG) log("activateSpeakerIfDocked()...");
2183 
2184         boolean activated = false;
2185         if (PhoneGlobals.mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
2186             if (DBG) log("activateSpeakerIfDocked(): In a dock -> may need to turn on speaker.");
2187             final PhoneGlobals app = PhoneGlobals.getInstance();
2188 
2189             // TODO: This function should move to AudioRouter
2190             final BluetoothManager btManager = app.getBluetoothManager();
2191             //final WiredHeadsetManager wiredHeadset = app.getWiredHeadsetManager();
2192             //final AudioRouter audioRouter = app.getAudioRouter();
2193 
2194             /*if (!wiredHeadset.isHeadsetPlugged() && !btManager.isBluetoothHeadsetAudioOn()) {
2195                 //audioRouter.setSpeaker(true);
2196                 activated = true;
2197             }*/
2198         }
2199         return activated;
2200     }
2201 
2202 
2203     /**
2204      * Returns whether the phone is in ECM ("Emergency Callback Mode") or not.
2205      */
isPhoneInEcm(Phone phone)2206     /* package */ static boolean isPhoneInEcm(Phone phone) {
2207         if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) {
2208             // For phones that support ECM, return true iff PROPERTY_INECM_MODE == "true".
2209             // TODO: There ought to be a better API for this than just
2210             // exposing a system property all the way up to the app layer,
2211             // probably a method like "inEcm()" provided by the telephony
2212             // layer.
2213             String ecmMode =
2214                     SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
2215             if (ecmMode != null) {
2216                 return ecmMode.equals("true");
2217             }
2218         }
2219         return false;
2220     }
2221 
2222     /**
2223      * Returns the most appropriate Phone object to handle a call
2224      * to the specified number.
2225      *
2226      * @param cm the CallManager.
2227      * @param scheme the scheme from the data URI that the number originally came from.
2228      * @param number the phone number, or SIP address.
2229      */
pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, String primarySipUri, ComponentName thirdPartyCallComponent)2230     public static Phone pickPhoneBasedOnNumber(CallManager cm, String scheme, String number,
2231             String primarySipUri, ComponentName thirdPartyCallComponent) {
2232         if (DBG) {
2233             log("pickPhoneBasedOnNumber: scheme " + scheme
2234                     + ", number " + toLogSafePhoneNumber(number)
2235                     + ", sipUri "
2236                     + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null")
2237                     + ", thirdPartyCallComponent: " + thirdPartyCallComponent);
2238         }
2239 
2240         if (primarySipUri != null) {
2241             Phone phone = getSipPhoneFromUri(cm, primarySipUri);
2242             if (phone != null) return phone;
2243         }
2244 
2245         return cm.getDefaultPhone();
2246     }
2247 
getSipPhoneFromUri(CallManager cm, String target)2248     public static Phone getSipPhoneFromUri(CallManager cm, String target) {
2249         for (Phone phone : cm.getAllPhones()) {
2250             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
2251                 String sipUri = ((SipPhone) phone).getSipUri();
2252                 if (target.equals(sipUri)) {
2253                     if (DBG) log("- pickPhoneBasedOnNumber:" +
2254                             "found SipPhone! obj = " + phone + ", "
2255                             + phone.getClass());
2256                     return phone;
2257                 }
2258             }
2259         }
2260         return null;
2261     }
2262 
2263     /**
2264      * Returns true when the given call is in INCOMING state and there's no foreground phone call,
2265      * meaning the call is the first real incoming call the phone is having.
2266      */
isRealIncomingCall(Call.State state)2267     public static boolean isRealIncomingCall(Call.State state) {
2268         return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
2269     }
2270 
getPresentationString(Context context, int presentation)2271     public static String getPresentationString(Context context, int presentation) {
2272         String name = context.getString(R.string.unknown);
2273         if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
2274             name = context.getString(R.string.private_num);
2275         } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
2276             name = context.getString(R.string.payphone);
2277         }
2278         return name;
2279     }
2280 
sendViewNotificationAsync(Context context, Uri contactUri)2281     public static void sendViewNotificationAsync(Context context, Uri contactUri) {
2282         if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")");
2283         Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri);
2284         intent.setClassName("com.android.contacts",
2285                 "com.android.contacts.ViewNotificationService");
2286         context.startService(intent);
2287     }
2288 
2289     //
2290     // General phone and call state debugging/testing code
2291     //
2292 
dumpCallState(Phone phone)2293     /* package */ static void dumpCallState(Phone phone) {
2294         PhoneGlobals app = PhoneGlobals.getInstance();
2295         Log.d(LOG_TAG, "dumpCallState():");
2296         Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
2297               + ", state = " + phone.getState());
2298 
2299         StringBuilder b = new StringBuilder(128);
2300 
2301         Call call = phone.getForegroundCall();
2302         b.setLength(0);
2303         b.append("  - FG call: ").append(call.getState());
2304         b.append(" isAlive ").append(call.getState().isAlive());
2305         b.append(" isRinging ").append(call.getState().isRinging());
2306         b.append(" isDialing ").append(call.getState().isDialing());
2307         b.append(" isIdle ").append(call.isIdle());
2308         b.append(" hasConnections ").append(call.hasConnections());
2309         Log.d(LOG_TAG, b.toString());
2310 
2311         call = phone.getBackgroundCall();
2312         b.setLength(0);
2313         b.append("  - BG call: ").append(call.getState());
2314         b.append(" isAlive ").append(call.getState().isAlive());
2315         b.append(" isRinging ").append(call.getState().isRinging());
2316         b.append(" isDialing ").append(call.getState().isDialing());
2317         b.append(" isIdle ").append(call.isIdle());
2318         b.append(" hasConnections ").append(call.hasConnections());
2319         Log.d(LOG_TAG, b.toString());
2320 
2321         call = phone.getRingingCall();
2322         b.setLength(0);
2323         b.append("  - RINGING call: ").append(call.getState());
2324         b.append(" isAlive ").append(call.getState().isAlive());
2325         b.append(" isRinging ").append(call.getState().isRinging());
2326         b.append(" isDialing ").append(call.getState().isDialing());
2327         b.append(" isIdle ").append(call.isIdle());
2328         b.append(" hasConnections ").append(call.hasConnections());
2329         Log.d(LOG_TAG, b.toString());
2330 
2331 
2332         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
2333         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
2334         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
2335         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2336         b.setLength(0);
2337         b.append("  - hasRingingCall ").append(hasRingingCall);
2338         b.append(" hasActiveCall ").append(hasActiveCall);
2339         b.append(" hasHoldingCall ").append(hasHoldingCall);
2340         b.append(" allLinesTaken ").append(allLinesTaken);
2341         Log.d(LOG_TAG, b.toString());
2342 
2343         // On CDMA phones, dump out the CdmaPhoneCallState too:
2344         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
2345             if (app.cdmaPhoneCallState != null) {
2346                 Log.d(LOG_TAG, "  - CDMA call state: "
2347                       + app.cdmaPhoneCallState.getCurrentCallState());
2348             } else {
2349                 Log.d(LOG_TAG, "  - CDMA device, but null cdmaPhoneCallState!");
2350             }
2351         }
2352     }
2353 
log(String msg)2354     private static void log(String msg) {
2355         Log.d(LOG_TAG, msg);
2356     }
2357 
dumpCallManager()2358     static void dumpCallManager() {
2359         Call call;
2360         CallManager cm = PhoneGlobals.getInstance().mCM;
2361         StringBuilder b = new StringBuilder(128);
2362 
2363 
2364 
2365         Log.d(LOG_TAG, "############### dumpCallManager() ##############");
2366         // TODO: Don't log "cm" itself, since CallManager.toString()
2367         // already spews out almost all this same information.
2368         // We should fix CallManager.toString() to be more minimal, and
2369         // use an explicit dumpState() method for the verbose dump.
2370         // Log.d(LOG_TAG, "CallManager: " + cm
2371         //         + ", state = " + cm.getState());
2372         Log.d(LOG_TAG, "CallManager: state = " + cm.getState());
2373         b.setLength(0);
2374         call = cm.getActiveFgCall();
2375         b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO ");
2376         b.append(call);
2377         b.append( "  State: ").append(cm.getActiveFgCallState());
2378         b.append( "  Conn: ").append(cm.getFgCallConnections());
2379         Log.d(LOG_TAG, b.toString());
2380         b.setLength(0);
2381         call = cm.getFirstActiveBgCall();
2382         b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO ");
2383         b.append(call);
2384         b.append( "  State: ").append(cm.getFirstActiveBgCall().getState());
2385         b.append( "  Conn: ").append(cm.getBgCallConnections());
2386         Log.d(LOG_TAG, b.toString());
2387         b.setLength(0);
2388         call = cm.getFirstActiveRingingCall();
2389         b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO ");
2390         b.append(call);
2391         b.append( "  State: ").append(cm.getFirstActiveRingingCall().getState());
2392         Log.d(LOG_TAG, b.toString());
2393 
2394 
2395 
2396         for (Phone phone : CallManager.getInstance().getAllPhones()) {
2397             if (phone != null) {
2398                 Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName()
2399                         + ", state = " + phone.getState());
2400                 b.setLength(0);
2401                 call = phone.getForegroundCall();
2402                 b.append(" - FG call: ").append(call);
2403                 b.append( "  State: ").append(call.getState());
2404                 b.append( "  Conn: ").append(call.hasConnections());
2405                 Log.d(LOG_TAG, b.toString());
2406                 b.setLength(0);
2407                 call = phone.getBackgroundCall();
2408                 b.append(" - BG call: ").append(call);
2409                 b.append( "  State: ").append(call.getState());
2410                 b.append( "  Conn: ").append(call.hasConnections());
2411                 Log.d(LOG_TAG, b.toString());b.setLength(0);
2412                 call = phone.getRingingCall();
2413                 b.append(" - RINGING call: ").append(call);
2414                 b.append( "  State: ").append(call.getState());
2415                 b.append( "  Conn: ").append(call.hasConnections());
2416                 Log.d(LOG_TAG, b.toString());
2417             }
2418         }
2419 
2420         Log.d(LOG_TAG, "############## END dumpCallManager() ###############");
2421     }
2422 
2423     /**
2424      * @return if the context is in landscape orientation.
2425      */
isLandscape(Context context)2426     public static boolean isLandscape(Context context) {
2427         return context.getResources().getConfiguration().orientation
2428                 == Configuration.ORIENTATION_LANDSCAPE;
2429     }
2430 }
2431