| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.phone; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.ProgressDialog; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.res.Resources; |
| import android.media.AudioAttributes; |
| import android.media.AudioManager; |
| import android.media.MediaPlayer; |
| import android.media.RingtoneManager; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PersistableBundle; |
| import android.os.UserHandle; |
| import android.os.VibrationEffect; |
| import android.os.Vibrator; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.VideoProfile; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.SubscriptionManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.ContextThemeWrapper; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.widget.EditText; |
| import android.widget.Toast; |
| |
| import com.android.internal.telephony.Call; |
| import com.android.internal.telephony.CallStateException; |
| import com.android.internal.telephony.Connection; |
| import com.android.internal.telephony.IccCard; |
| import com.android.internal.telephony.MmiCode; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneConstants; |
| import com.android.internal.telephony.PhoneFactory; |
| import com.android.internal.telephony.TelephonyCapabilities; |
| import com.android.phone.settings.SuppServicesUiUtil; |
| import com.android.telephony.Rlog; |
| |
| import java.io.IOException; |
| import java.util.List; |
| |
| /** |
| * Misc utilities for the Phone app. |
| */ |
| public class PhoneUtils { |
| public static final String EMERGENCY_ACCOUNT_HANDLE_ID = "E"; |
| private static final String LOG_TAG = "PhoneUtils"; |
| private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2); |
| |
| // Do not check in with VDBG = true, since that may write PII to the system log. |
| private static final boolean VDBG = false; |
| |
| // Return codes from placeCall() |
| public static final int CALL_STATUS_DIALED = 0; // The number was successfully dialed |
| public static final int CALL_STATUS_DIALED_MMI = 1; // The specified number was an MMI code |
| public static final int CALL_STATUS_FAILED = 2; // The call failed |
| |
| // USSD string length for MMI operations |
| static final int MIN_USSD_LEN = 1; |
| static final int MAX_USSD_LEN = 160; |
| |
| /** Define for not a special CNAP string */ |
| private static final int CNAP_SPECIAL_CASE_NO = -1; |
| |
| /** Define for default vibrate pattern if res cannot be found */ |
| private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; |
| |
| /** |
| * Theme to use for dialogs displayed by utility methods in this class. This is needed |
| * because these dialogs are displayed using the application context, which does not resolve |
| * the dialog theme correctly. |
| */ |
| private static final int THEME = com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert; |
| |
| /** USSD information used to aggregate all USSD messages */ |
| private static StringBuilder sUssdMsg = new StringBuilder(); |
| |
| private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT = |
| new ComponentName("com.android.phone", |
| "com.android.services.telephony.TelephonyConnectionService"); |
| |
| /** This class is never instantiated. */ |
| private PhoneUtils() { |
| } |
| |
| /** |
| * For a CDMA phone, advance the call state upon making a new |
| * outgoing call. |
| * |
| * <pre> |
| * IDLE -> SINGLE_ACTIVE |
| * or |
| * SINGLE_ACTIVE -> THRWAY_ACTIVE |
| * </pre> |
| * @param app The phone instance. |
| */ |
| private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, |
| Connection connection) { |
| if (app.cdmaPhoneCallState.getCurrentCallState() == |
| CdmaPhoneCallState.PhoneCallState.IDLE) { |
| // This is the first outgoing call. Set the Phone Call State to ACTIVE |
| app.cdmaPhoneCallState.setCurrentCallState( |
| CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE); |
| } else { |
| // This is the second outgoing call. Set the Phone Call State to 3WAY |
| app.cdmaPhoneCallState.setCurrentCallState( |
| CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE); |
| |
| // TODO: Remove this code. |
| //app.getCallModeler().setCdmaOutgoing3WayCall(connection); |
| } |
| } |
| |
| /** |
| * Dial the number using the phone passed in. |
| * |
| * @param context To perform the CallerInfo query. |
| * @param phone the Phone object. |
| * @param number to be dialed as requested by the user. This is |
| * NOT the phone number to connect to. It is used only to build the |
| * call card and to update the call log. See above for restrictions. |
| * |
| * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED |
| */ |
| public static int placeOtaspCall(Context context, Phone phone, String number) { |
| final Uri gatewayUri = null; |
| |
| if (VDBG) { |
| log("placeCall()... number: '" + number + "'" |
| + ", GW:'" + gatewayUri + "'"); |
| } else { |
| log("placeCall()... number: " + toLogSafePhoneNumber(number) |
| + ", GW: " + (gatewayUri != null ? "non-null" : "null")); |
| } |
| final PhoneGlobals app = PhoneGlobals.getInstance(); |
| |
| boolean useGateway = false; |
| Uri contactRef = null; |
| |
| int status = CALL_STATUS_DIALED; |
| Connection connection; |
| String numberToDial; |
| numberToDial = number; |
| |
| try { |
| connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY); |
| } catch (CallStateException ex) { |
| // CallStateException means a new outgoing call is not currently |
| // possible: either no more call slots exist, or there's another |
| // call already in the process of dialing or ringing. |
| Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex); |
| return CALL_STATUS_FAILED; |
| |
| // Note that it's possible for CallManager.dial() to return |
| // null *without* throwing an exception; that indicates that |
| // we dialed an MMI (see below). |
| } |
| |
| int phoneType = phone.getPhoneType(); |
| |
| // On GSM phones, null is returned for MMI codes |
| if (null == connection) { |
| status = CALL_STATUS_FAILED; |
| } else { |
| if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { |
| updateCdmaCallStateOnNewOutgoingCall(app, connection); |
| } |
| } |
| |
| return status; |
| } |
| |
| /* package */ static String toLogSafePhoneNumber(String number) { |
| // For unknown number, log empty string. |
| if (number == null) { |
| return ""; |
| } |
| |
| if (VDBG) { |
| // When VDBG is true we emit PII. |
| return number; |
| } |
| |
| // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare |
| // sanitized phone numbers. |
| StringBuilder builder = new StringBuilder(); |
| for (int i = 0; i < number.length(); i++) { |
| char c = number.charAt(i); |
| if (c == '-' || c == '@' || c == '.') { |
| builder.append(c); |
| } else { |
| builder.append('x'); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| /** |
| * Handle the MMIInitiate message and put up an alert that lets |
| * the user cancel the operation, if applicable. |
| * |
| * @param context context to get strings. |
| * @param mmiCode the MmiCode object being started. |
| * @param buttonCallbackMessage message to post when button is clicked. |
| * @param previousAlert a previous alert used in this activity. |
| * @return the dialog handle |
| */ |
| static Dialog displayMMIInitiate(Context context, |
| MmiCode mmiCode, |
| Message buttonCallbackMessage, |
| Dialog previousAlert) { |
| log("displayMMIInitiate: " + Rlog.pii(LOG_TAG, mmiCode.toString())); |
| if (previousAlert != null) { |
| previousAlert.dismiss(); |
| } |
| |
| // The UI paradigm we are using now requests that all dialogs have |
| // user interaction, and that any other messages to the user should |
| // be by way of Toasts. |
| // |
| // In adhering to this request, all MMI initiating "OK" dialogs |
| // (non-cancelable MMIs) that end up being closed when the MMI |
| // completes (thereby showing a completion dialog) are being |
| // replaced with Toasts. |
| // |
| // As a side effect, moving to Toasts for the non-cancelable MMIs |
| // also means that buttonCallbackMessage (which was tied into "OK") |
| // is no longer invokable for these dialogs. This is not a problem |
| // since the only callback messages we supported were for cancelable |
| // MMIs anyway. |
| // |
| // A cancelable MMI is really just a USSD request. The term |
| // "cancelable" here means that we can cancel the request when the |
| // system prompts us for a response, NOT while the network is |
| // processing the MMI request. Any request to cancel a USSD while |
| // the network is NOT ready for a response may be ignored. |
| // |
| // With this in mind, we replace the cancelable alert dialog with |
| // a progress dialog, displayed until we receive a request from |
| // the the network. For more information, please see the comments |
| // in the displayMMIComplete() method below. |
| // |
| // Anything that is NOT a USSD request is a normal MMI request, |
| // which will bring up a toast (desribed above). |
| |
| boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable(); |
| |
| if (!isCancelable) { |
| log("displayMMIInitiate: not a USSD code, displaying status toast."); |
| CharSequence text = context.getText(R.string.mmiStarted); |
| Toast.makeText(context, text, Toast.LENGTH_SHORT) |
| .show(); |
| return null; |
| } else { |
| log("displayMMIInitiate: running USSD code, displaying intermediate progress."); |
| |
| // create the indeterminate progress dialog and display it. |
| ProgressDialog pd = new ProgressDialog(context, THEME); |
| pd.setMessage(context.getText(R.string.ussdRunning)); |
| pd.setCancelable(false); |
| pd.setIndeterminate(true); |
| pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); |
| |
| pd.show(); |
| |
| return pd; |
| } |
| |
| } |
| |
| /** |
| * Handle the MMIComplete message and fire off an intent to display |
| * the message. |
| * |
| * @param context context to get strings. |
| * @param mmiCode MMI result. |
| * @param previousAlert a previous alert used in this activity. |
| */ |
| static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, |
| Message dismissCallbackMessage, |
| AlertDialog previousAlert) { |
| final PhoneGlobals app = PhoneGlobals.getInstance(); |
| CharSequence text; |
| int title = 0; // title for the progress dialog, if needed. |
| MmiCode.State state = mmiCode.getState(); |
| |
| log("displayMMIComplete: state=" + state); |
| |
| switch (state) { |
| case PENDING: |
| // USSD code asking for feedback from user. |
| text = mmiCode.getMessage(); |
| log("displayMMIComplete: using text from PENDING MMI message: '" + text + "'"); |
| break; |
| case CANCELLED: |
| text = null; |
| break; |
| case COMPLETE: |
| PersistableBundle b = null; |
| if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) { |
| b = app.getCarrierConfigForSubId( |
| phone.getSubId()); |
| } else { |
| b = app.getCarrierConfig(); |
| } |
| |
| if (b.getBoolean(CarrierConfigManager.KEY_USE_CALLER_ID_USSD_BOOL)) { |
| text = SuppServicesUiUtil.handleCallerIdUssdResponse(app, context, phone, |
| mmiCode); |
| if (mmiCode.getMessage() != null && !text.equals(mmiCode.getMessage())) { |
| break; |
| } |
| } |
| |
| if (app.getPUKEntryActivity() != null) { |
| // if an attempt to unPUK the device was made, we specify |
| // the title and the message here. |
| title = com.android.internal.R.string.PinMmi; |
| text = context.getText(R.string.puk_unlocked); |
| break; |
| } |
| // All other conditions for the COMPLETE mmi state will cause |
| // the case to fall through to message logic in common with |
| // the FAILED case. |
| |
| case FAILED: |
| text = mmiCode.getMessage(); |
| log("displayMMIComplete (failed): using text from MMI message: '" + text + "'"); |
| break; |
| default: |
| throw new IllegalStateException("Unexpected MmiCode state: " + state); |
| } |
| |
| if (previousAlert != null) { |
| previousAlert.dismiss(); |
| } |
| |
| // Check to see if a UI exists for the PUK activation. If it does |
| // exist, then it indicates that we're trying to unblock the PUK. |
| if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) { |
| log("displaying PUK unblocking progress dialog."); |
| |
| // create the progress dialog, make sure the flags and type are |
| // set correctly. |
| ProgressDialog pd = new ProgressDialog(app, THEME); |
| pd.setTitle(title); |
| pd.setMessage(text); |
| pd.setCancelable(false); |
| pd.setIndeterminate(true); |
| pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); |
| pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); |
| |
| // display the dialog |
| pd.show(); |
| |
| // indicate to the Phone app that the progress dialog has |
| // been assigned for the PUK unlock / SIM READY process. |
| app.setPukEntryProgressDialog(pd); |
| |
| } else if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.FAILED)) { |
| createUssdDialog(app, context, text, phone, |
| WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); |
| // In case of failure to unlock, we'll need to reset the |
| // PUK unlock activity, so that the user may try again. |
| app.setPukEntryActivity(null); |
| } else { |
| // In case of failure to unlock, we'll need to reset the |
| // PUK unlock activity, so that the user may try again. |
| if (app.getPUKEntryActivity() != null) { |
| app.setPukEntryActivity(null); |
| } |
| |
| // A USSD in a pending state means that it is still |
| // interacting with the user. |
| if (state != MmiCode.State.PENDING) { |
| createUssdDialog(app, context, text, phone, |
| WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| } else { |
| log("displayMMIComplete: USSD code has requested user input. Constructing input " |
| + "dialog."); |
| |
| // USSD MMI code that is interacting with the user. The |
| // basic set of steps is this: |
| // 1. User enters a USSD request |
| // 2. We recognize the request and displayMMIInitiate |
| // (above) creates a progress dialog. |
| // 3. Request returns and we get a PENDING or COMPLETE |
| // message. |
| // 4. These MMI messages are caught in the PhoneApp |
| // (onMMIComplete) and the InCallScreen |
| // (mHandler.handleMessage) which bring up this dialog |
| // and closes the original progress dialog, |
| // respectively. |
| // 5. If the message is anything other than PENDING, |
| // we are done, and the alert dialog (directly above) |
| // displays the outcome. |
| // 6. If the network is requesting more information from |
| // the user, the MMI will be in a PENDING state, and |
| // we display this dialog with the message. |
| // 7. User input, or cancel requests result in a return |
| // to step 1. Keep in mind that this is the only |
| // time that a USSD should be canceled. |
| |
| // inflate the layout with the scrolling text area for the dialog. |
| ContextThemeWrapper contextThemeWrapper = |
| new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme); |
| LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null); |
| |
| // get the input field. |
| final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field); |
| |
| // specify the dialog's click listener, with SEND and CANCEL logic. |
| final DialogInterface.OnClickListener mUSSDDialogListener = |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int whichButton) { |
| switch (whichButton) { |
| case DialogInterface.BUTTON_POSITIVE: |
| // As per spec 24.080, valid length of ussd string |
| // is 1 - 160. If length is out of the range then |
| // display toast message & Cancel MMI operation. |
| if (inputText.length() < MIN_USSD_LEN |
| || inputText.length() > MAX_USSD_LEN) { |
| Toast.makeText(app, |
| app.getResources().getString(R.string.enter_input, |
| MIN_USSD_LEN, MAX_USSD_LEN), |
| Toast.LENGTH_LONG).show(); |
| if (mmiCode.isCancelable()) { |
| mmiCode.cancel(); |
| } |
| } else { |
| phone.sendUssdResponse(inputText.getText().toString()); |
| } |
| break; |
| case DialogInterface.BUTTON_NEGATIVE: |
| if (mmiCode.isCancelable()) { |
| mmiCode.cancel(); |
| } |
| break; |
| } |
| } |
| }; |
| |
| // build the dialog |
| final AlertDialog newDialog = |
| new AlertDialog.Builder(contextThemeWrapper) |
| .setMessage(text) |
| .setView(dialogView) |
| .setPositiveButton(R.string.send_button, mUSSDDialogListener) |
| .setNegativeButton(R.string.cancel, mUSSDDialogListener) |
| .setCancelable(false) |
| .create(); |
| |
| // attach the key listener to the dialog's input field and make |
| // sure focus is set. |
| final View.OnKeyListener mUSSDDialogInputListener = |
| new View.OnKeyListener() { |
| public boolean onKey(View v, int keyCode, KeyEvent event) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_CALL: |
| case KeyEvent.KEYCODE_ENTER: |
| if(event.getAction() == KeyEvent.ACTION_DOWN) { |
| phone.sendUssdResponse(inputText.getText().toString()); |
| newDialog.dismiss(); |
| } |
| return true; |
| } |
| return false; |
| } |
| }; |
| inputText.setOnKeyListener(mUSSDDialogInputListener); |
| inputText.requestFocus(); |
| |
| // set the window properties of the dialog |
| newDialog.getWindow().setType( |
| WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); |
| newDialog.getWindow().addFlags( |
| WindowManager.LayoutParams.FLAG_DIM_BEHIND); |
| |
| // now show the dialog! |
| newDialog.show(); |
| |
| newDialog.getButton(DialogInterface.BUTTON_POSITIVE) |
| .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); |
| newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) |
| .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); |
| } |
| |
| if (mmiCode.isNetworkInitiatedUssd()) { |
| playSound(context); |
| } |
| } |
| } |
| |
| private static void playSound(Context context) { |
| AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); |
| int callsRingerMode = audioManager.getRingerMode(); |
| |
| if (callsRingerMode == AudioManager.RINGER_MODE_NORMAL) { |
| log("playSound : RINGER_MODE_NORMAL"); |
| try { |
| Uri notificationUri = RingtoneManager.getDefaultUri( |
| RingtoneManager.TYPE_NOTIFICATION); |
| MediaPlayer mediaPlayer = new MediaPlayer(); |
| mediaPlayer.setDataSource(context, notificationUri); |
| AudioAttributes aa = new AudioAttributes.Builder() |
| .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION) |
| .setUsage(AudioAttributes.USAGE_NOTIFICATION) |
| .build(); |
| mediaPlayer.setAudioAttributes(aa); |
| mediaPlayer.setLooping(false); |
| mediaPlayer.prepare(); |
| mediaPlayer.start(); |
| } catch (IOException e) { |
| log("playSound exception : " + e); |
| } |
| } else if (callsRingerMode == AudioManager.RINGER_MODE_VIBRATE) { |
| log("playSound : RINGER_MODE_VIBRATE"); |
| Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); |
| // Use NotificationManagerService#DEFAULT_VIBRATE_PATTERN if |
| // R.array.config_defaultNotificationVibePattern is not defined. |
| long[] pattern = getLongArray(context.getResources(), |
| R.array.config_defaultNotificationVibePattern, DEFAULT_VIBRATE_PATTERN); |
| vibrator.vibrate(VibrationEffect.createWaveform(pattern, -1), |
| new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) |
| .build()); |
| } |
| } |
| |
| private static long[] getLongArray(Resources r, int resid, long[] def) { |
| int[] ar = r.getIntArray(resid); |
| if (ar == null) { |
| return def; |
| } |
| final int len = ar.length; |
| long[] out = new long[len]; |
| for (int i = 0; i < len; i++) { |
| out[i] = ar[i]; |
| } |
| return out; |
| } |
| |
| /** |
| * It displays the message dialog for user about the mmi code result message. |
| * |
| * @param app This is {@link PhoneGlobals} |
| * @param context Context to get strings. |
| * @param text This is message's result. |
| * @param phone This is phone to create sssd dialog. |
| * @param windowType The new window type. {@link WindowManager.LayoutParams}. |
| */ |
| public static void createUssdDialog(PhoneGlobals app, Context context, CharSequence text, |
| Phone phone, int windowType) { |
| log("displayMMIComplete: MMI code has finished running."); |
| |
| log("displayMMIComplete: Extended NW displayMMIInitiate (" + text + ")"); |
| if (text == null || text.length() == 0) { |
| return; |
| } |
| |
| // displaying system alert dialog on the screen instead of |
| // using another activity to display the message. This |
| // places the message at the forefront of the UI. |
| AlertDialog ussdDialog = new AlertDialog.Builder(context, THEME) |
| .setPositiveButton(R.string.ok, null) |
| .setCancelable(true) |
| .setOnDismissListener(new DialogInterface.OnDismissListener() { |
| @Override |
| public void onDismiss(DialogInterface dialog) { |
| sUssdMsg.setLength(0); |
| } |
| }) |
| .create(); |
| |
| ussdDialog.getWindow().setType(windowType); |
| ussdDialog.getWindow().addFlags( |
| WindowManager.LayoutParams.FLAG_DIM_BEHIND); |
| |
| if (sUssdMsg.length() != 0) { |
| sUssdMsg.insert(0, "\n") |
| .insert(0, app.getResources().getString(R.string.ussd_dialog_sep)) |
| .insert(0, "\n"); |
| } |
| if (phone != null && phone.getCarrierName() != null) { |
| ussdDialog.setTitle(app.getResources().getString(R.string.carrier_mmi_msg_title, |
| phone.getCarrierName())); |
| } else { |
| ussdDialog |
| .setTitle(app.getResources().getString(R.string.default_carrier_mmi_msg_title)); |
| } |
| sUssdMsg.insert(0, text); |
| ussdDialog.setMessage(sUssdMsg.toString()); |
| ussdDialog.show(); |
| } |
| |
| /** |
| * Cancels the current pending MMI operation, if applicable. |
| * @return true if we canceled an MMI operation, or false |
| * if the current pending MMI wasn't cancelable |
| * or if there was no current pending MMI at all. |
| * |
| * @see displayMMIInitiate |
| */ |
| static boolean cancelMmiCode(Phone phone) { |
| List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); |
| int count = pendingMmis.size(); |
| if (DBG) log("cancelMmiCode: num pending MMIs = " + count); |
| |
| boolean canceled = false; |
| if (count > 0) { |
| // assume that we only have one pending MMI operation active at a time. |
| // I don't think it's possible to enter multiple MMI codes concurrently |
| // in the phone UI, because during the MMI operation, an Alert panel |
| // is displayed, which prevents more MMI code from being entered. |
| MmiCode mmiCode = pendingMmis.get(0); |
| if (mmiCode.isCancelable()) { |
| mmiCode.cancel(); |
| canceled = true; |
| } |
| } |
| return canceled; |
| } |
| |
| |
| // |
| // Misc UI policy helper functions |
| // |
| |
| /** |
| * Check if a phone number can be route through a 3rd party |
| * gateway. The number must be a global phone number in numerical |
| * form (1-800-666-SEXY won't work). |
| * |
| * MMI codes and the like cannot be used as a dial number for the |
| * gateway either. |
| * |
| * @param number To be dialed via a 3rd party gateway. |
| * @return true If the number can be routed through the 3rd party network. |
| */ |
| private static boolean isRoutableViaGateway(String number) { |
| if (TextUtils.isEmpty(number)) { |
| return false; |
| } |
| number = PhoneNumberUtils.stripSeparators(number); |
| if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { |
| return false; |
| } |
| number = PhoneNumberUtils.extractNetworkPortion(number); |
| return PhoneNumberUtils.isGlobalPhoneNumber(number); |
| } |
| |
| /** |
| * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. |
| */ |
| /* package */ static boolean isPhoneInEcm(Phone phone) { |
| if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { |
| return phone.isInEcm(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true when the given call is in INCOMING state and there's no foreground phone call, |
| * meaning the call is the first real incoming call the phone is having. |
| */ |
| public static boolean isRealIncomingCall(Call.State state) { |
| return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); |
| } |
| |
| // |
| // General phone and call state debugging/testing code |
| // |
| |
| private static void log(String msg) { |
| Log.d(LOG_TAG, msg); |
| } |
| |
| public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { |
| return makePstnPhoneAccountHandleWithPrefix(phone, "", |
| false, phone.getUserHandle()); |
| } |
| |
| public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( |
| Phone phone, String prefix, boolean isEmergency, UserHandle userHandle) { |
| // TODO: Should use some sort of special hidden flag to decorate this account as |
| // an emergency-only account |
| String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + |
| String.valueOf(phone.getSubId()); |
| return makePstnPhoneAccountHandleWithId(id, userHandle); |
| } |
| |
| public static PhoneAccountHandle makePstnPhoneAccountHandleWithId( |
| String id, UserHandle userHandle) { |
| ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); |
| // If user handle is null, resort to default constructor to use phone process's |
| // user handle |
| return userHandle == null |
| ? new PhoneAccountHandle(pstnConnectionServiceName, id) |
| : new PhoneAccountHandle(pstnConnectionServiceName, id, userHandle); |
| } |
| |
| public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { |
| if (phoneAccount != null |
| && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { |
| return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); |
| } |
| return SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| } |
| |
| public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { |
| Phone phone = getPhoneForPhoneAccountHandle(handle); |
| if (phone != null) { |
| return phone.getSubId(); |
| } |
| return SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| } |
| |
| public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) { |
| if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { |
| return getPhoneFromSubId(handle.getId()); |
| } |
| return null; |
| } |
| |
| /** |
| * Determine if a given phone account corresponds to an active SIM |
| * |
| * @param sm An instance of the subscription manager so it is not recreated for each calling of |
| * this method. |
| * @param handle The handle for the phone account to check |
| * @return {@code true} If there is an active SIM for this phone account, |
| * {@code false} otherwise. |
| */ |
| public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { |
| return sm.getActiveSubscriptionInfo(Integer.parseInt(handle.getId())) != null; |
| } |
| |
| private static ComponentName getPstnConnectionServiceName() { |
| return PSTN_CONNECTION_SERVICE_COMPONENT; |
| } |
| |
| private static Phone getPhoneFromSubId(String subId) { |
| if (!TextUtils.isEmpty(subId)) { |
| for (Phone phone : PhoneFactory.getPhones()) { |
| String phoneSubId = Integer.toString(phone.getSubId()); |
| if (subId.equals(phoneSubId)) { |
| return phone; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Register ICC status for all phones. |
| */ |
| static final void registerIccStatus(Handler handler, int event) { |
| for (Phone phone : PhoneFactory.getPhones()) { |
| IccCard sim = phone.getIccCard(); |
| if (sim != null) { |
| if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); |
| sim.registerForNetworkLocked(handler, event, phone); |
| } |
| } |
| } |
| |
| /** |
| * Register ICC status for all phones. |
| */ |
| static final void registerIccStatus(Handler handler, int event, int phoneId) { |
| Phone[] phones = PhoneFactory.getPhones(); |
| IccCard sim = phones[phoneId].getIccCard(); |
| if (sim != null) { |
| if (VDBG) { |
| Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId()); |
| } |
| sim.registerForNetworkLocked(handler, event, phones[phoneId]); |
| } |
| } |
| |
| /** |
| * Unregister ICC status for a specific phone. |
| */ |
| static final void unregisterIccStatus(Handler handler, int phoneId) { |
| Phone[] phones = PhoneFactory.getPhones(); |
| IccCard sim = phones[phoneId].getIccCard(); |
| if (sim != null) { |
| if (VDBG) { |
| Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId()); |
| } |
| sim.unregisterForNetworkLocked(handler); |
| } |
| } |
| |
| /** |
| * Set the radio power on/off state for all phones. |
| * |
| * @param enabled true means on, false means off. |
| */ |
| static final void setRadioPower(boolean enabled) { |
| for (Phone phone : PhoneFactory.getPhones()) { |
| phone.setRadioPower(enabled); |
| } |
| } |
| } |