• 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.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.net.Uri;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.os.PersistableBundle;
29 import android.telecom.PhoneAccount;
30 import android.telecom.PhoneAccountHandle;
31 import android.telecom.VideoProfile;
32 import android.telephony.CarrierConfigManager;
33 import android.telephony.PhoneNumberUtils;
34 import android.telephony.SubscriptionManager;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.ContextThemeWrapper;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.WindowManager;
42 import android.widget.EditText;
43 import android.widget.Toast;
44 
45 import com.android.internal.telephony.Call;
46 import com.android.internal.telephony.CallManager;
47 import com.android.internal.telephony.CallStateException;
48 import com.android.internal.telephony.CallerInfo;
49 import com.android.internal.telephony.CallerInfoAsyncQuery;
50 import com.android.internal.telephony.Connection;
51 import com.android.internal.telephony.IccCard;
52 import com.android.internal.telephony.MmiCode;
53 import com.android.internal.telephony.Phone;
54 import com.android.internal.telephony.PhoneConstants;
55 import com.android.internal.telephony.PhoneFactory;
56 import com.android.internal.telephony.TelephonyCapabilities;
57 import com.android.phone.CallGatewayManager.RawGatewayInfo;
58 import com.android.phone.settings.SuppServicesUiUtil;
59 
60 import java.util.Arrays;
61 import java.util.List;
62 
63 /**
64  * Misc utilities for the Phone app.
65  */
66 public class PhoneUtils {
67     public static final String EMERGENCY_ACCOUNT_HANDLE_ID = "E";
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     // Return codes from placeCall()
75     public static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
76     public static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
77     public static final int CALL_STATUS_FAILED = 2;  // The call failed
78 
79     // USSD string length for MMI operations
80     static final int MIN_USSD_LEN = 1;
81     static final int MAX_USSD_LEN = 160;
82 
83     /** Define for not a special CNAP string */
84     private static final int CNAP_SPECIAL_CASE_NO = -1;
85 
86     /**
87      * Theme to use for dialogs displayed by utility methods in this class. This is needed
88      * because these dialogs are displayed using the application context, which does not resolve
89      * the dialog theme correctly.
90      */
91     private static final int THEME = com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert;
92 
93     /** USSD information used to aggregate all USSD messages */
94     private static AlertDialog sUssdDialog = null;
95     private static StringBuilder sUssdMsg = new StringBuilder();
96 
97     private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
98             new ComponentName("com.android.phone",
99                     "com.android.services.telephony.TelephonyConnectionService");
100 
101     /** This class is never instantiated. */
PhoneUtils()102     private PhoneUtils() {
103     }
104 
105     /**
106      * For a CDMA phone, advance the call state upon making a new
107      * outgoing call.
108      *
109      * <pre>
110      *   IDLE -> SINGLE_ACTIVE
111      * or
112      *   SINGLE_ACTIVE -> THRWAY_ACTIVE
113      * </pre>
114      * @param app The phone instance.
115      */
updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, Connection connection)116     private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app,
117             Connection connection) {
118         if (app.cdmaPhoneCallState.getCurrentCallState() ==
119             CdmaPhoneCallState.PhoneCallState.IDLE) {
120             // This is the first outgoing call. Set the Phone Call State to ACTIVE
121             app.cdmaPhoneCallState.setCurrentCallState(
122                 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
123         } else {
124             // This is the second outgoing call. Set the Phone Call State to 3WAY
125             app.cdmaPhoneCallState.setCurrentCallState(
126                 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
127 
128             // TODO: Remove this code.
129             //app.getCallModeler().setCdmaOutgoing3WayCall(connection);
130         }
131     }
132 
133     /**
134      * @see placeCall below
135      */
placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall)136     public static int placeCall(Context context, Phone phone, String number, Uri contactRef,
137             boolean isEmergencyCall) {
138         return placeCall(context, phone, number, contactRef, isEmergencyCall,
139                 CallGatewayManager.EMPTY_INFO, null);
140     }
141 
142     /**
143      * Dial the number using the phone passed in.
144      *
145      * If the connection is establised, this method issues a sync call
146      * that may block to query the caller info.
147      * TODO: Change the logic to use the async query.
148      *
149      * @param context To perform the CallerInfo query.
150      * @param phone the Phone object.
151      * @param number to be dialed as requested by the user. This is
152      * NOT the phone number to connect to. It is used only to build the
153      * call card and to update the call log. See above for restrictions.
154      * @param contactRef that triggered the call. Typically a 'tel:'
155      * uri but can also be a 'content://contacts' one.
156      * @param isEmergencyCall indicates that whether or not this is an
157      * emergency call
158      * @param gatewayUri Is the address used to setup the connection, null
159      * if not using a gateway
160      * @param callGateway Class for setting gateway data on a successful call.
161      *
162      * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED
163      */
placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway)164     public static int placeCall(Context context, Phone phone, String number, Uri contactRef,
165             boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) {
166         final Uri gatewayUri = gatewayInfo.gatewayUri;
167 
168         if (VDBG) {
169             log("placeCall()... number: '" + number + "'"
170                     + ", GW:'" + gatewayUri + "'"
171                     + ", contactRef:" + contactRef
172                     + ", isEmergencyCall: " + isEmergencyCall);
173         } else {
174             log("placeCall()... number: " + toLogSafePhoneNumber(number)
175                     + ", GW: " + (gatewayUri != null ? "non-null" : "null")
176                     + ", emergency? " + isEmergencyCall);
177         }
178         final PhoneGlobals app = PhoneGlobals.getInstance();
179 
180         boolean useGateway = false;
181         if (null != gatewayUri &&
182             !isEmergencyCall &&
183             PhoneUtils.isRoutableViaGateway(number)) {  // Filter out MMI, OTA and other codes.
184             useGateway = true;
185         }
186 
187         int status = CALL_STATUS_DIALED;
188         Connection connection;
189         String numberToDial;
190         if (useGateway) {
191             // TODO: 'tel' should be a constant defined in framework base
192             // somewhere (it is in webkit.)
193             if (null == gatewayUri || !PhoneAccount.SCHEME_TEL.equals(gatewayUri.getScheme())) {
194                 Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri);
195                 return CALL_STATUS_FAILED;
196             }
197 
198             // We can use getSchemeSpecificPart because we don't allow #
199             // in the gateway numbers (treated a fragment delim.) However
200             // if we allow more complex gateway numbers sequence (with
201             // passwords or whatnot) that use #, this may break.
202             // TODO: Need to support MMI codes.
203             numberToDial = gatewayUri.getSchemeSpecificPart();
204         } else {
205             numberToDial = number;
206         }
207 
208         try {
209             connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY);
210         } catch (CallStateException ex) {
211             // CallStateException means a new outgoing call is not currently
212             // possible: either no more call slots exist, or there's another
213             // call already in the process of dialing or ringing.
214             Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex);
215             return CALL_STATUS_FAILED;
216 
217             // Note that it's possible for CallManager.dial() to return
218             // null *without* throwing an exception; that indicates that
219             // we dialed an MMI (see below).
220         }
221 
222         int phoneType = phone.getPhoneType();
223 
224         // On GSM phones, null is returned for MMI codes
225         if (null == connection) {
226             status = CALL_STATUS_FAILED;
227         } else {
228             if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
229                 updateCdmaCallStateOnNewOutgoingCall(app, connection);
230             }
231 
232             if (gatewayUri == null) {
233                 // phone.dial() succeeded: we're now in a normal phone call.
234                 // attach the URI to the CallerInfo Object if it is there,
235                 // otherwise just attach the Uri Reference.
236                 // if the uri does not have a "content" scheme, then we treat
237                 // it as if it does NOT have a unique reference.
238                 String content = context.getContentResolver().SCHEME_CONTENT;
239                 if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
240                     Object userDataObject = connection.getUserData();
241                     if (userDataObject == null) {
242                         connection.setUserData(contactRef);
243                     } else {
244                         // TODO: This branch is dead code, we have
245                         // just created the connection which has
246                         // no user data (null) by default.
247                         if (userDataObject instanceof CallerInfo) {
248                         ((CallerInfo) userDataObject).contactRefUri = contactRef;
249                         } else {
250                         ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =
251                             contactRef;
252                         }
253                     }
254                 }
255             }
256 
257             startGetCallerInfo(context, connection, null, null, gatewayInfo);
258         }
259 
260         return status;
261     }
262 
toLogSafePhoneNumber(String number)263     /* package */ static String toLogSafePhoneNumber(String number) {
264         // For unknown number, log empty string.
265         if (number == null) {
266             return "";
267         }
268 
269         if (VDBG) {
270             // When VDBG is true we emit PII.
271             return number;
272         }
273 
274         // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
275         // sanitized phone numbers.
276         StringBuilder builder = new StringBuilder();
277         for (int i = 0; i < number.length(); i++) {
278             char c = number.charAt(i);
279             if (c == '-' || c == '@' || c == '.') {
280                 builder.append(c);
281             } else {
282                 builder.append('x');
283             }
284         }
285         return builder.toString();
286     }
287 
288     /**
289      * Handle the MMIInitiate message and put up an alert that lets
290      * the user cancel the operation, if applicable.
291      *
292      * @param context context to get strings.
293      * @param mmiCode the MmiCode object being started.
294      * @param buttonCallbackMessage message to post when button is clicked.
295      * @param previousAlert a previous alert used in this activity.
296      * @return the dialog handle
297      */
displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)298     static Dialog displayMMIInitiate(Context context,
299                                           MmiCode mmiCode,
300                                           Message buttonCallbackMessage,
301                                           Dialog previousAlert) {
302         log("displayMMIInitiate: " + android.telecom.Log.pii(mmiCode.toString()));
303         if (previousAlert != null) {
304             previousAlert.dismiss();
305         }
306 
307         // The UI paradigm we are using now requests that all dialogs have
308         // user interaction, and that any other messages to the user should
309         // be by way of Toasts.
310         //
311         // In adhering to this request, all MMI initiating "OK" dialogs
312         // (non-cancelable MMIs) that end up being closed when the MMI
313         // completes (thereby showing a completion dialog) are being
314         // replaced with Toasts.
315         //
316         // As a side effect, moving to Toasts for the non-cancelable MMIs
317         // also means that buttonCallbackMessage (which was tied into "OK")
318         // is no longer invokable for these dialogs.  This is not a problem
319         // since the only callback messages we supported were for cancelable
320         // MMIs anyway.
321         //
322         // A cancelable MMI is really just a USSD request. The term
323         // "cancelable" here means that we can cancel the request when the
324         // system prompts us for a response, NOT while the network is
325         // processing the MMI request.  Any request to cancel a USSD while
326         // the network is NOT ready for a response may be ignored.
327         //
328         // With this in mind, we replace the cancelable alert dialog with
329         // a progress dialog, displayed until we receive a request from
330         // the the network.  For more information, please see the comments
331         // in the displayMMIComplete() method below.
332         //
333         // Anything that is NOT a USSD request is a normal MMI request,
334         // which will bring up a toast (desribed above).
335 
336         boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
337 
338         if (!isCancelable) {
339             log("displayMMIInitiate: not a USSD code, displaying status toast.");
340             CharSequence text = context.getText(R.string.mmiStarted);
341             Toast.makeText(context, text, Toast.LENGTH_SHORT)
342                 .show();
343             return null;
344         } else {
345             log("displayMMIInitiate: running USSD code, displaying intermediate progress.");
346 
347             // create the indeterminate progress dialog and display it.
348             ProgressDialog pd = new ProgressDialog(context, THEME);
349             pd.setMessage(context.getText(R.string.ussdRunning));
350             pd.setCancelable(false);
351             pd.setIndeterminate(true);
352             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
353 
354             pd.show();
355 
356             return pd;
357         }
358 
359     }
360 
361     /**
362      * Handle the MMIComplete message and fire off an intent to display
363      * the message.
364      *
365      * @param context context to get strings.
366      * @param mmiCode MMI result.
367      * @param previousAlert a previous alert used in this activity.
368      */
displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)369     static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
370             Message dismissCallbackMessage,
371             AlertDialog previousAlert) {
372         final PhoneGlobals app = PhoneGlobals.getInstance();
373         CharSequence text;
374         int title = 0;  // title for the progress dialog, if needed.
375         MmiCode.State state = mmiCode.getState();
376 
377         log("displayMMIComplete: state=" + state);
378 
379         switch (state) {
380             case PENDING:
381                 // USSD code asking for feedback from user.
382                 text = mmiCode.getMessage();
383                 log("displayMMIComplete: using text from PENDING MMI message: '" + text + "'");
384                 break;
385             case CANCELLED:
386                 text = null;
387                 break;
388             case COMPLETE:
389                 PersistableBundle b = null;
390                 if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) {
391                     b = app.getCarrierConfigForSubId(
392                             phone.getSubId());
393                 } else {
394                     b = app.getCarrierConfig();
395                 }
396 
397                 if (b.getBoolean(CarrierConfigManager.KEY_USE_CALLER_ID_USSD_BOOL)) {
398                     text = SuppServicesUiUtil.handleCallerIdUssdResponse(app, context, phone,
399                             mmiCode);
400                     if (mmiCode.getMessage() != null && !text.equals(mmiCode.getMessage())) {
401                         break;
402                     }
403                 }
404 
405                 if (app.getPUKEntryActivity() != null) {
406                     // if an attempt to unPUK the device was made, we specify
407                     // the title and the message here.
408                     title = com.android.internal.R.string.PinMmi;
409                     text = context.getText(R.string.puk_unlocked);
410                     break;
411                 }
412                 // All other conditions for the COMPLETE mmi state will cause
413                 // the case to fall through to message logic in common with
414                 // the FAILED case.
415 
416             case FAILED:
417                 text = mmiCode.getMessage();
418                 log("displayMMIComplete (failed): using text from MMI message: '" + text + "'");
419                 break;
420             default:
421                 throw new IllegalStateException("Unexpected MmiCode state: " + state);
422         }
423 
424         if (previousAlert != null) {
425             previousAlert.dismiss();
426         }
427 
428         // Check to see if a UI exists for the PUK activation.  If it does
429         // exist, then it indicates that we're trying to unblock the PUK.
430         if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
431             if (DBG) log("displaying PUK unblocking progress dialog.");
432 
433             // create the progress dialog, make sure the flags and type are
434             // set correctly.
435             ProgressDialog pd = new ProgressDialog(app, THEME);
436             pd.setTitle(title);
437             pd.setMessage(text);
438             pd.setCancelable(false);
439             pd.setIndeterminate(true);
440             pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
441             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
442 
443             // display the dialog
444             pd.show();
445 
446             // indicate to the Phone app that the progress dialog has
447             // been assigned for the PUK unlock / SIM READY process.
448             app.setPukEntryProgressDialog(pd);
449 
450         } else if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.FAILED)) {
451             createUssdDialog(app, context, text,
452                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
453             // In case of failure to unlock, we'll need to reset the
454             // PUK unlock activity, so that the user may try again.
455             app.setPukEntryActivity(null);
456         } else {
457             // In case of failure to unlock, we'll need to reset the
458             // PUK unlock activity, so that the user may try again.
459             if (app.getPUKEntryActivity() != null) {
460                 app.setPukEntryActivity(null);
461             }
462 
463             // A USSD in a pending state means that it is still
464             // interacting with the user.
465             if (state != MmiCode.State.PENDING) {
466                 createUssdDialog(app, context, text,
467                         WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
468             } else {
469                 log("displayMMIComplete: USSD code has requested user input. Constructing input "
470                         + "dialog.");
471 
472                 // USSD MMI code that is interacting with the user.  The
473                 // basic set of steps is this:
474                 //   1. User enters a USSD request
475                 //   2. We recognize the request and displayMMIInitiate
476                 //      (above) creates a progress dialog.
477                 //   3. Request returns and we get a PENDING or COMPLETE
478                 //      message.
479                 //   4. These MMI messages are caught in the PhoneApp
480                 //      (onMMIComplete) and the InCallScreen
481                 //      (mHandler.handleMessage) which bring up this dialog
482                 //      and closes the original progress dialog,
483                 //      respectively.
484                 //   5. If the message is anything other than PENDING,
485                 //      we are done, and the alert dialog (directly above)
486                 //      displays the outcome.
487                 //   6. If the network is requesting more information from
488                 //      the user, the MMI will be in a PENDING state, and
489                 //      we display this dialog with the message.
490                 //   7. User input, or cancel requests result in a return
491                 //      to step 1.  Keep in mind that this is the only
492                 //      time that a USSD should be canceled.
493 
494                 // inflate the layout with the scrolling text area for the dialog.
495                 ContextThemeWrapper contextThemeWrapper =
496                         new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme);
497                 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService(
498                         Context.LAYOUT_INFLATER_SERVICE);
499                 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
500 
501                 // get the input field.
502                 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
503 
504                 // specify the dialog's click listener, with SEND and CANCEL logic.
505                 final DialogInterface.OnClickListener mUSSDDialogListener =
506                     new DialogInterface.OnClickListener() {
507                         public void onClick(DialogInterface dialog, int whichButton) {
508                             switch (whichButton) {
509                                 case DialogInterface.BUTTON_POSITIVE:
510                                     // As per spec 24.080, valid length of ussd string
511                                     // is 1 - 160. If length is out of the range then
512                                     // display toast message & Cancel MMI operation.
513                                     if (inputText.length() < MIN_USSD_LEN
514                                             || inputText.length() > MAX_USSD_LEN) {
515                                         Toast.makeText(app,
516                                                 app.getResources().getString(R.string.enter_input,
517                                                 MIN_USSD_LEN, MAX_USSD_LEN),
518                                                 Toast.LENGTH_LONG).show();
519                                         if (mmiCode.isCancelable()) {
520                                             mmiCode.cancel();
521                                         }
522                                     } else {
523                                         phone.sendUssdResponse(inputText.getText().toString());
524                                     }
525                                     break;
526                                 case DialogInterface.BUTTON_NEGATIVE:
527                                     if (mmiCode.isCancelable()) {
528                                         mmiCode.cancel();
529                                     }
530                                     break;
531                             }
532                         }
533                     };
534 
535                 // build the dialog
536                 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper)
537                         .setMessage(text)
538                         .setView(dialogView)
539                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
540                         .setNegativeButton(R.string.cancel, mUSSDDialogListener)
541                         .setCancelable(false)
542                         .create();
543 
544                 // attach the key listener to the dialog's input field and make
545                 // sure focus is set.
546                 final View.OnKeyListener mUSSDDialogInputListener =
547                     new View.OnKeyListener() {
548                         public boolean onKey(View v, int keyCode, KeyEvent event) {
549                             switch (keyCode) {
550                                 case KeyEvent.KEYCODE_CALL:
551                                 case KeyEvent.KEYCODE_ENTER:
552                                     if(event.getAction() == KeyEvent.ACTION_DOWN) {
553                                         phone.sendUssdResponse(inputText.getText().toString());
554                                         newDialog.dismiss();
555                                     }
556                                     return true;
557                             }
558                             return false;
559                         }
560                     };
561                 inputText.setOnKeyListener(mUSSDDialogInputListener);
562                 inputText.requestFocus();
563 
564                 // set the window properties of the dialog
565                 newDialog.getWindow().setType(
566                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
567                 newDialog.getWindow().addFlags(
568                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
569 
570                 // now show the dialog!
571                 newDialog.show();
572 
573                 newDialog.getButton(DialogInterface.BUTTON_POSITIVE)
574                         .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
575                 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE)
576                         .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
577             }
578         }
579     }
580 
581     /**
582      * It displays the message dialog for user about the mmi code result message.
583      *
584      * @param app This is {@link PhoneGlobals}
585      * @param context Context to get strings.
586      * @param text This is message's result.
587      * @param windowType The new window type. {@link WindowManager.LayoutParams}.
588      */
createUssdDialog(PhoneGlobals app, Context context, CharSequence text, int windowType)589     public static void createUssdDialog(PhoneGlobals app, Context context, CharSequence text,
590             int windowType) {
591         log("displayMMIComplete: MMI code has finished running.");
592 
593         log("displayMMIComplete: Extended NW displayMMIInitiate (" + text + ")");
594         if (text == null || text.length() == 0) {
595             return;
596         }
597 
598         // displaying system alert dialog on the screen instead of
599         // using another activity to display the message.  This
600         // places the message at the forefront of the UI.
601 
602         if (sUssdDialog == null) {
603             sUssdDialog = new AlertDialog.Builder(context, THEME)
604                     .setPositiveButton(R.string.ok, null)
605                     .setCancelable(true)
606                     .setOnDismissListener(new DialogInterface.OnDismissListener() {
607                         @Override
608                         public void onDismiss(DialogInterface dialog) {
609                             sUssdMsg.setLength(0);
610                         }
611                     })
612                     .create();
613 
614             sUssdDialog.getWindow().setType(windowType);
615             sUssdDialog.getWindow().addFlags(
616                     WindowManager.LayoutParams.FLAG_DIM_BEHIND);
617         }
618         if (sUssdMsg.length() != 0) {
619             sUssdMsg
620                     .insert(0, "\n")
621                     .insert(0, app.getResources().getString(R.string.ussd_dialog_sep))
622                     .insert(0, "\n");
623         }
624         sUssdMsg.insert(0, text);
625         sUssdDialog.setMessage(sUssdMsg.toString());
626         sUssdDialog.show();
627     }
628 
629     /**
630      * Cancels the current pending MMI operation, if applicable.
631      * @return true if we canceled an MMI operation, or false
632      *         if the current pending MMI wasn't cancelable
633      *         or if there was no current pending MMI at all.
634      *
635      * @see displayMMIInitiate
636      */
cancelMmiCode(Phone phone)637     static boolean cancelMmiCode(Phone phone) {
638         List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
639         int count = pendingMmis.size();
640         if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
641 
642         boolean canceled = false;
643         if (count > 0) {
644             // assume that we only have one pending MMI operation active at a time.
645             // I don't think it's possible to enter multiple MMI codes concurrently
646             // in the phone UI, because during the MMI operation, an Alert panel
647             // is displayed, which prevents more MMI code from being entered.
648             MmiCode mmiCode = pendingMmis.get(0);
649             if (mmiCode.isCancelable()) {
650                 mmiCode.cancel();
651                 canceled = true;
652             }
653         }
654         return canceled;
655     }
656 
657     /**
658      * Returns the caller-id info corresponding to the specified Connection.
659      * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
660      * extract a phone number from the specified Connection, and feed that
661      * number into CallerInfo.getCallerInfo().)
662      *
663      * The returned CallerInfo may be null in certain error cases, like if the
664      * specified Connection was null, or if we weren't able to get a valid
665      * phone number from the Connection.
666      *
667      * Finally, if the getCallerInfo() call did succeed, we save the resulting
668      * CallerInfo object in the "userData" field of the Connection.
669      *
670      * NOTE: This API should be avoided, with preference given to the
671      * asynchronous startGetCallerInfo API.
672      */
getCallerInfo(Context context, Connection c)673     static CallerInfo getCallerInfo(Context context, Connection c) {
674         CallerInfo info = null;
675 
676         if (c != null) {
677             //See if there is a URI attached.  If there is, this means
678             //that there is no CallerInfo queried yet, so we'll need to
679             //replace the URI with a full CallerInfo object.
680             Object userDataObject = c.getUserData();
681             if (userDataObject instanceof Uri) {
682                 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
683                 if (info != null) {
684                     c.setUserData(info);
685                 }
686             } else {
687                 if (userDataObject instanceof CallerInfoToken) {
688                     //temporary result, while query is running
689                     info = ((CallerInfoToken) userDataObject).currentInfo;
690                 } else {
691                     //final query result
692                     info = (CallerInfo) userDataObject;
693                 }
694                 if (info == null) {
695                     // No URI, or Existing CallerInfo, so we'll have to make do with
696                     // querying a new CallerInfo using the connection's phone number.
697                     String number = c.getAddress();
698 
699                     if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number));
700 
701                     if (!TextUtils.isEmpty(number)) {
702                         info = CallerInfo.getCallerInfo(context, number);
703                         if (info != null) {
704                             c.setUserData(info);
705                         }
706                     }
707                 }
708             }
709         }
710         return info;
711     }
712 
713     /**
714      * Class returned by the startGetCallerInfo call to package a temporary
715      * CallerInfo Object, to be superceded by the CallerInfo Object passed
716      * into the listener when the query with token mAsyncQueryToken is complete.
717      */
718     public static class CallerInfoToken {
719         /**indicates that there will no longer be updates to this request.*/
720         public boolean isFinal;
721 
722         public CallerInfo currentInfo;
723         public CallerInfoAsyncQuery asyncQuery;
724     }
725 
726     /**
727      * place a temporary callerinfo object in the hands of the caller and notify
728      * caller when the actual query is done.
729      */
startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)730     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
731             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie,
732             RawGatewayInfo info) {
733         CallerInfoToken cit;
734 
735         if (c == null) {
736             //TODO: perhaps throw an exception here.
737             cit = new CallerInfoToken();
738             cit.asyncQuery = null;
739             return cit;
740         }
741 
742         Object userDataObject = c.getUserData();
743 
744         // There are now 3 states for the Connection's userData object:
745         //
746         //   (1) Uri - query has not been executed yet
747         //
748         //   (2) CallerInfoToken - query is executing, but has not completed.
749         //
750         //   (3) CallerInfo - query has executed.
751         //
752         // In each case we have slightly different behaviour:
753         //   1. If the query has not been executed yet (Uri or null), we start
754         //      query execution asynchronously, and note it by attaching a
755         //      CallerInfoToken as the userData.
756         //   2. If the query is executing (CallerInfoToken), we've essentially
757         //      reached a state where we've received multiple requests for the
758         //      same callerInfo.  That means that once the query is complete,
759         //      we'll need to execute the additional listener requested.
760         //   3. If the query has already been executed (CallerInfo), we just
761         //      return the CallerInfo object as expected.
762         //   4. Regarding isFinal - there are cases where the CallerInfo object
763         //      will not be attached, like when the number is empty (caller id
764         //      blocking).  This flag is used to indicate that the
765         //      CallerInfoToken object is going to be permanent since no
766         //      query results will be returned.  In the case where a query
767         //      has been completed, this flag is used to indicate to the caller
768         //      that the data will not be updated since it is valid.
769         //
770         //      Note: For the case where a number is NOT retrievable, we leave
771         //      the CallerInfo as null in the CallerInfoToken.  This is
772         //      something of a departure from the original code, since the old
773         //      code manufactured a CallerInfo object regardless of the query
774         //      outcome.  From now on, we will append an empty CallerInfo
775         //      object, to mirror previous behaviour, and to avoid Null Pointer
776         //      Exceptions.
777 
778         if (userDataObject instanceof Uri) {
779             // State (1): query has not been executed yet
780 
781             //create a dummy callerinfo, populate with what we know from URI.
782             cit = new CallerInfoToken();
783             cit.currentInfo = new CallerInfo();
784             cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
785                     (Uri) userDataObject, sCallerInfoQueryListener, c);
786             cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
787             cit.isFinal = false;
788 
789             c.setUserData(cit);
790 
791             if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
792 
793         } else if (userDataObject == null) {
794             // No URI, or Existing CallerInfo, so we'll have to make do with
795             // querying a new CallerInfo using the connection's phone number.
796             String number = c.getAddress();
797 
798             if (info != null && info != CallGatewayManager.EMPTY_INFO) {
799                 // Gateway number, the connection number is actually the gateway number.
800                 // need to lookup via dialed number.
801                 number = info.trueNumber;
802             }
803 
804             if (DBG) {
805                 log("PhoneUtils.startGetCallerInfo: new query for phone number...");
806                 log("- number (address): " + toLogSafePhoneNumber(number));
807                 log("- c: " + c);
808                 log("- phone: " + c.getCall().getPhone());
809                 int phoneType = c.getCall().getPhone().getPhoneType();
810                 log("- phoneType: " + phoneType);
811                 switch (phoneType) {
812                     case PhoneConstants.PHONE_TYPE_NONE: log("  ==> PHONE_TYPE_NONE"); break;
813                     case PhoneConstants.PHONE_TYPE_GSM: log("  ==> PHONE_TYPE_GSM"); break;
814                     case PhoneConstants.PHONE_TYPE_IMS: log("  ==> PHONE_TYPE_IMS"); break;
815                     case PhoneConstants.PHONE_TYPE_CDMA: log("  ==> PHONE_TYPE_CDMA"); break;
816                     case PhoneConstants.PHONE_TYPE_SIP: log("  ==> PHONE_TYPE_SIP"); break;
817                     case PhoneConstants.PHONE_TYPE_THIRD_PARTY:
818                         log("  ==> PHONE_TYPE_THIRD_PARTY");
819                         break;
820                     default: log("  ==> Unknown phone type"); break;
821                 }
822             }
823 
824             cit = new CallerInfoToken();
825             cit.currentInfo = new CallerInfo();
826 
827             // Store CNAP information retrieved from the Connection (we want to do this
828             // here regardless of whether the number is empty or not).
829             cit.currentInfo.cnapName =  c.getCnapName();
830             cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
831                                                              // by ContactInfo later
832             cit.currentInfo.numberPresentation = c.getNumberPresentation();
833             cit.currentInfo.namePresentation = c.getCnapNamePresentation();
834 
835             if (VDBG) {
836                 log("startGetCallerInfo: number = " + number);
837                 log("startGetCallerInfo: CNAP Info from FW(1): name="
838                     + cit.currentInfo.cnapName
839                     + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
840             }
841 
842             // handling case where number is null (caller id hidden) as well.
843             if (!TextUtils.isEmpty(number)) {
844                 // Check for special CNAP cases and modify the CallerInfo accordingly
845                 // to be sure we keep the right information to display/log later
846                 number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
847                         cit.currentInfo.numberPresentation);
848 
849                 cit.currentInfo.phoneNumber = number;
850                 // For scenarios where we may receive a valid number from the network but a
851                 // restricted/unavailable presentation, we do not want to perform a contact query
852                 // (see note on isFinal above). So we set isFinal to true here as well.
853                 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
854                     cit.isFinal = true;
855                 } else {
856                     if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()...");
857                     cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
858                             number, sCallerInfoQueryListener, c);
859                     cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
860                     cit.isFinal = false;
861                 }
862             } else {
863                 // This is the case where we are querying on a number that
864                 // is null or empty, like a caller whose caller id is
865                 // blocked or empty (CLIR).  The previous behaviour was to
866                 // throw a null CallerInfo object back to the user, but
867                 // this departure is somewhat cleaner.
868                 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
869                 cit.isFinal = true; // please see note on isFinal, above.
870             }
871 
872             c.setUserData(cit);
873 
874             if (DBG) {
875                 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number));
876             }
877 
878         } else if (userDataObject instanceof CallerInfoToken) {
879             // State (2): query is executing, but has not completed.
880 
881             // just tack on this listener to the queue.
882             cit = (CallerInfoToken) userDataObject;
883 
884             // handling case where number is null (caller id hidden) as well.
885             if (cit.asyncQuery != null) {
886                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
887 
888                 if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
889                         listener.getClass().toString());
890             } else {
891                 // handling case where number/name gets updated later on by the network
892                 String updatedNumber = c.getAddress();
893 
894                 if (info != null) {
895                     // Gateway number, the connection number is actually the gateway number.
896                     // need to lookup via dialed number.
897                     updatedNumber = info.trueNumber;
898                 }
899 
900                 if (DBG) {
901                     log("startGetCallerInfo: updatedNumber initially = "
902                             + toLogSafePhoneNumber(updatedNumber));
903                 }
904                 if (!TextUtils.isEmpty(updatedNumber)) {
905                     // Store CNAP information retrieved from the Connection
906                     cit.currentInfo.cnapName =  c.getCnapName();
907                     // This can still get overwritten by ContactInfo
908                     cit.currentInfo.name = cit.currentInfo.cnapName;
909                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
910                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
911 
912                     updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
913                             updatedNumber, cit.currentInfo.numberPresentation);
914 
915                     cit.currentInfo.phoneNumber = updatedNumber;
916                     if (DBG) {
917                         log("startGetCallerInfo: updatedNumber="
918                                 + toLogSafePhoneNumber(updatedNumber));
919                     }
920                     if (VDBG) {
921                         log("startGetCallerInfo: CNAP Info from FW(2): name="
922                                 + cit.currentInfo.cnapName
923                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
924                     } else if (DBG) {
925                         log("startGetCallerInfo: CNAP Info from FW(2)");
926                     }
927                     // For scenarios where we may receive a valid number from the network but a
928                     // restricted/unavailable presentation, we do not want to perform a contact query
929                     // (see note on isFinal above). So we set isFinal to true here as well.
930                     if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
931                         cit.isFinal = true;
932                     } else {
933                         cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
934                                 updatedNumber, sCallerInfoQueryListener, c);
935                         cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
936                         cit.isFinal = false;
937                     }
938                 } else {
939                     if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
940                     if (cit.currentInfo == null) {
941                         cit.currentInfo = new CallerInfo();
942                     }
943                     // Store CNAP information retrieved from the Connection
944                     cit.currentInfo.cnapName = c.getCnapName();  // This can still get
945                                                                  // overwritten by ContactInfo
946                     cit.currentInfo.name = cit.currentInfo.cnapName;
947                     cit.currentInfo.numberPresentation = c.getNumberPresentation();
948                     cit.currentInfo.namePresentation = c.getCnapNamePresentation();
949 
950                     if (VDBG) {
951                         log("startGetCallerInfo: CNAP Info from FW(3): name="
952                                 + cit.currentInfo.cnapName
953                                 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
954                     } else if (DBG) {
955                         log("startGetCallerInfo: CNAP Info from FW(3)");
956                     }
957                     cit.isFinal = true; // please see note on isFinal, above.
958                 }
959             }
960         } else {
961             // State (3): query is complete.
962 
963             // The connection's userDataObject is a full-fledged
964             // CallerInfo instance.  Wrap it in a CallerInfoToken and
965             // return it to the user.
966 
967             cit = new CallerInfoToken();
968             cit.currentInfo = (CallerInfo) userDataObject;
969             cit.asyncQuery = null;
970             cit.isFinal = true;
971             // since the query is already done, call the listener.
972             if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
973             if (DBG) log("==> cit.currentInfo = " + cit.currentInfo);
974         }
975         return cit;
976     }
977 
978     /**
979      * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that
980      * we use with all our CallerInfoAsyncQuery.startQuery() requests.
981      */
982     private static final int QUERY_TOKEN = -1;
983     static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
984         new CallerInfoAsyncQuery.OnQueryCompleteListener () {
985             /**
986              * When the query completes, we stash the resulting CallerInfo
987              * object away in the Connection's "userData" (where it will
988              * later be retrieved by the in-call UI.)
989              */
990             public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
991                 if (DBG) log("query complete, updating connection.userdata");
992                 Connection conn = (Connection) cookie;
993 
994                 // Added a check if CallerInfo is coming from ContactInfo or from Connection.
995                 // If no ContactInfo, then we want to use CNAP information coming from network
996                 if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
997                 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
998                     // If the number presentation has not been set by
999                     // the ContactInfo, use the one from the
1000                     // connection.
1001 
1002                     // TODO: Need a new util method to merge the info
1003                     // from the Connection in a CallerInfo object.
1004                     // Here 'ci' is a new CallerInfo instance read
1005                     // from the DB. It has lost all the connection
1006                     // info preset before the query (see PhoneUtils
1007                     // line 1334). We should have a method to merge
1008                     // back into this new instance the info from the
1009                     // connection object not set by the DB. If the
1010                     // Connection already has a CallerInfo instance in
1011                     // userData, then we could use this instance to
1012                     // fill 'ci' in. The same routine could be used in
1013                     // PhoneUtils.
1014                     if (0 == ci.numberPresentation) {
1015                         ci.numberPresentation = conn.getNumberPresentation();
1016                     }
1017                 } else {
1018                     // No matching contact was found for this number.
1019                     // Return a new CallerInfo based solely on the CNAP
1020                     // information from the network.
1021 
1022                     CallerInfo newCi = getCallerInfo(null, conn);
1023 
1024                     // ...but copy over the (few) things we care about
1025                     // from the original CallerInfo object:
1026                     if (newCi != null) {
1027                         newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1028                         newCi.geoDescription = ci.geoDescription; // To get geo description string
1029                         ci = newCi;
1030                     }
1031                 }
1032 
1033                 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection...");
1034                 conn.setUserData(ci);
1035             }
1036         };
1037 
1038 
1039     /**
1040      * Returns a single "name" for the specified given a CallerInfo object.
1041      * If the name is null, return defaultString as the default value, usually
1042      * context.getString(R.string.unknown).
1043      */
getCompactNameFromCallerInfo(CallerInfo ci, Context context)1044     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1045         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1046 
1047         String compactName = null;
1048         if (ci != null) {
1049             if (TextUtils.isEmpty(ci.name)) {
1050                 // Perform any modifications for special CNAP cases to
1051                 // the phone number being displayed, if applicable.
1052                 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber,
1053                                                         ci.numberPresentation);
1054             } else {
1055                 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795.
1056                 compactName = ci.name;
1057             }
1058         }
1059 
1060         if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1061             // If we're still null/empty here, then check if we have a presentation
1062             // string that takes precedence that we could return, otherwise display
1063             // "unknown" string.
1064             if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
1065                 compactName = context.getString(R.string.private_num);
1066             } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) {
1067                 compactName = context.getString(R.string.payphone);
1068             } else {
1069                 compactName = context.getString(R.string.unknown);
1070             }
1071         }
1072         if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
1073         return compactName;
1074     }
1075 
isInEmergencyCall(CallManager cm)1076     static boolean isInEmergencyCall(CallManager cm) {
1077         Call fgCall = cm.getActiveFgCall();
1078         // isIdle includes checks for the DISCONNECTING/DISCONNECTED state.
1079         if(!fgCall.isIdle()) {
1080             for (Connection cn : fgCall.getConnections()) {
1081                 if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
1082                         cn.getAddress())) {
1083                     return true;
1084                 }
1085             }
1086         }
1087         return false;
1088     }
1089 
1090     //
1091     // Misc UI policy helper functions
1092     //
1093 
1094     /**
1095      * Based on the input CNAP number string,
1096      * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
1097      * Otherwise, return CNAP_SPECIAL_CASE_NO.
1098      */
checkCnapSpecialCases(String n)1099     private static int checkCnapSpecialCases(String n) {
1100         if (n.equals("PRIVATE") ||
1101                 n.equals("P") ||
1102                 n.equals("RES")) {
1103             if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
1104             return PhoneConstants.PRESENTATION_RESTRICTED;
1105         } else if (n.equals("UNAVAILABLE") ||
1106                 n.equals("UNKNOWN") ||
1107                 n.equals("UNA") ||
1108                 n.equals("U")) {
1109             if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
1110             return PhoneConstants.PRESENTATION_UNKNOWN;
1111         } else {
1112             if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
1113             return CNAP_SPECIAL_CASE_NO;
1114         }
1115     }
1116 
1117     /**
1118      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
1119      * from the network to indicate different number presentations, convert them to
1120      * expected number and presentation values within the CallerInfo object.
1121      * @param number number we use to verify if we are in a corner case
1122      * @param presentation presentation value used to verify if we are in a corner case
1123      * @return the new String that should be used for the phone number
1124      */
modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)1125     /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
1126             String number, int presentation) {
1127         // Obviously we return number if ci == null, but still return number if
1128         // number == null, because in these cases the correct string will still be
1129         // displayed/logged after this function returns based on the presentation value.
1130         if (ci == null || number == null) return number;
1131 
1132         if (DBG) {
1133             log("modifyForSpecialCnapCases: initially, number="
1134                     + toLogSafePhoneNumber(number)
1135                     + ", presentation=" + presentation + " ci " + ci);
1136         }
1137 
1138         // "ABSENT NUMBER" is a possible value we could get from the network as the
1139         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
1140         // and fix the presentation to be the same.
1141         final String[] absentNumberValues =
1142                 context.getResources().getStringArray(R.array.absent_num);
1143         if (Arrays.asList(absentNumberValues).contains(number)
1144                 && presentation == PhoneConstants.PRESENTATION_ALLOWED) {
1145             number = context.getString(R.string.unknown);
1146             ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
1147         }
1148 
1149         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
1150         // cases only apply if we received an allowed presentation from the network, so check
1151         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
1152         // match the presentation passed in for verification (meaning we changed it previously
1153         // because it's a corner case and we're being called from a different entry point).
1154         if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED
1155                 || (ci.numberPresentation != presentation
1156                         && presentation == PhoneConstants.PRESENTATION_ALLOWED)) {
1157             int cnapSpecialCase = checkCnapSpecialCases(number);
1158             if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
1159                 // For all special strings, change number & numberPresentation.
1160                 if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) {
1161                     number = context.getString(R.string.private_num);
1162                 } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) {
1163                     number = context.getString(R.string.unknown);
1164                 }
1165                 if (DBG) {
1166                     log("SpecialCnap: number=" + toLogSafePhoneNumber(number)
1167                             + "; presentation now=" + cnapSpecialCase);
1168                 }
1169                 ci.numberPresentation = cnapSpecialCase;
1170             }
1171         }
1172         if (DBG) {
1173             log("modifyForSpecialCnapCases: returning number string="
1174                     + toLogSafePhoneNumber(number));
1175         }
1176         return number;
1177     }
1178 
1179     //
1180     // Support for 3rd party phone service providers.
1181     //
1182 
1183     /**
1184      * Check if a phone number can be route through a 3rd party
1185      * gateway. The number must be a global phone number in numerical
1186      * form (1-800-666-SEXY won't work).
1187      *
1188      * MMI codes and the like cannot be used as a dial number for the
1189      * gateway either.
1190      *
1191      * @param number To be dialed via a 3rd party gateway.
1192      * @return true If the number can be routed through the 3rd party network.
1193      */
isRoutableViaGateway(String number)1194     private static boolean isRoutableViaGateway(String number) {
1195         if (TextUtils.isEmpty(number)) {
1196             return false;
1197         }
1198         number = PhoneNumberUtils.stripSeparators(number);
1199         if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) {
1200             return false;
1201         }
1202         number = PhoneNumberUtils.extractNetworkPortion(number);
1203         return PhoneNumberUtils.isGlobalPhoneNumber(number);
1204     }
1205 
1206     /**
1207      * Returns whether the phone is in ECM ("Emergency Callback Mode") or not.
1208      */
isPhoneInEcm(Phone phone)1209     /* package */ static boolean isPhoneInEcm(Phone phone) {
1210         if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) {
1211             return phone.isInEcm();
1212         }
1213         return false;
1214     }
1215 
1216     /**
1217      * Returns true when the given call is in INCOMING state and there's no foreground phone call,
1218      * meaning the call is the first real incoming call the phone is having.
1219      */
isRealIncomingCall(Call.State state)1220     public static boolean isRealIncomingCall(Call.State state) {
1221         return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
1222     }
1223 
1224     //
1225     // General phone and call state debugging/testing code
1226     //
1227 
log(String msg)1228     private static void log(String msg) {
1229         Log.d(LOG_TAG, msg);
1230     }
1231 
makePstnPhoneAccountHandle(String id)1232     public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) {
1233         return makePstnPhoneAccountHandleWithPrefix(id, "", false);
1234     }
1235 
makePstnPhoneAccountHandle(int phoneId)1236     public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
1237         return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
1238     }
1239 
makePstnPhoneAccountHandle(Phone phone)1240     public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
1241         return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
1242     }
1243 
makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)1244     public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
1245             Phone phone, String prefix, boolean isEmergency) {
1246         // TODO: Should use some sort of special hidden flag to decorate this account as
1247         // an emergency-only account
1248         String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix +
1249                 String.valueOf(phone.getFullIccSerialNumber());
1250         return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency);
1251     }
1252 
makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)1253     public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
1254             String id, String prefix, boolean isEmergency) {
1255         ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
1256         return new PhoneAccountHandle(pstnConnectionServiceName, id);
1257     }
1258 
getSubIdForPhoneAccount(PhoneAccount phoneAccount)1259     public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
1260         if (phoneAccount != null
1261                 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
1262             return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle());
1263         }
1264         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1265     }
1266 
getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)1267     public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) {
1268         Phone phone = getPhoneForPhoneAccountHandle(handle);
1269         if (phone != null) {
1270             return phone.getSubId();
1271         }
1272         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1273     }
1274 
getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)1275     public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) {
1276         if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
1277             return getPhoneFromIccId(handle.getId());
1278         }
1279         return null;
1280     }
1281 
1282     /**
1283      * Determine if a given phone account corresponds to an active SIM
1284      *
1285      * @param sm An instance of the subscription manager so it is not recreated for each calling of
1286      * this method.
1287      * @param handle The handle for the phone account to check
1288      * @return {@code true} If there is an active SIM for this phone account,
1289      * {@code false} otherwise.
1290      */
isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)1291     public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) {
1292         return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null;
1293     }
1294 
getPstnConnectionServiceName()1295     private static ComponentName getPstnConnectionServiceName() {
1296         return PSTN_CONNECTION_SERVICE_COMPONENT;
1297     }
1298 
getPhoneFromIccId(String iccId)1299     private static Phone getPhoneFromIccId(String iccId) {
1300         if (!TextUtils.isEmpty(iccId)) {
1301             for (Phone phone : PhoneFactory.getPhones()) {
1302                 String phoneIccId = phone.getFullIccSerialNumber();
1303                 if (iccId.equals(phoneIccId)) {
1304                     return phone;
1305                 }
1306             }
1307         }
1308         return null;
1309     }
1310 
1311     /**
1312      * Register ICC status for all phones.
1313      */
registerIccStatus(Handler handler, int event)1314     static final void registerIccStatus(Handler handler, int event) {
1315         for (Phone phone : PhoneFactory.getPhones()) {
1316             IccCard sim = phone.getIccCard();
1317             if (sim != null) {
1318                 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId());
1319                 sim.registerForNetworkLocked(handler, event, phone);
1320             }
1321         }
1322     }
1323 
1324     /**
1325      * Register ICC status for all phones.
1326      */
registerIccStatus(Handler handler, int event, int phoneId)1327     static final void registerIccStatus(Handler handler, int event, int phoneId) {
1328         Phone[] phones = PhoneFactory.getPhones();
1329         IccCard sim = phones[phoneId].getIccCard();
1330         if (sim != null) {
1331             if (VDBG) {
1332                 Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId());
1333             }
1334             sim.registerForNetworkLocked(handler, event, phones[phoneId]);
1335         }
1336     }
1337 
1338     /**
1339      * Unregister ICC status for a specific phone.
1340      */
unregisterIccStatus(Handler handler, int phoneId)1341     static final void unregisterIccStatus(Handler handler, int phoneId) {
1342         Phone[] phones = PhoneFactory.getPhones();
1343         IccCard sim = phones[phoneId].getIccCard();
1344         if (sim != null) {
1345             if (VDBG) {
1346                 Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId());
1347             }
1348             sim.unregisterForNetworkLocked(handler);
1349         }
1350     }
1351 
1352     /**
1353      * Set the radio power on/off state for all phones.
1354      *
1355      * @param enabled true means on, false means off.
1356      */
setRadioPower(boolean enabled)1357     static final void setRadioPower(boolean enabled) {
1358         for (Phone phone : PhoneFactory.getPhones()) {
1359             phone.setRadioPower(enabled);
1360         }
1361     }
1362 }
1363